Fixed beacon implementation, pstrotator settings, Variable handling, Shortcut and snipped preferences ui, beacon handling, AS Settings and all fitting manual texts

This commit is contained in:
Marc Froehlich
2026-07-26 09:49:50 +02:00
parent 00496b56e9
commit ee974bbd73
12 changed files with 1092 additions and 572 deletions
@@ -1,122 +1,178 @@
package kst4contest.controller;
import java.util.Arrays;
import java.util.TimerTask;
import kst4contest.model.ChatMessage;
import kst4contest.model.ThreadStateMessage;
/**
* This class is for sending beacons intervalled to the public chat. Gets all
* preferences and instances via the Chatpreferences-object of the
* Chatcontroller.
* <br/><br/>
* The task will be runned out of the singleton ChatController instance in an
* intervall as specified by the Chatpreferences-instance (typically as
* configured in the xml file.
*
*
* @author prakt
* Sends the configured public-chat beacons for both active chat categories.
*
* <p>Both categories deliberately share one timer and therefore the same
* interval. Their enable flags and message templates remain independent. Every
* run reads the current preferences, resolves global message variables and
* sends only the categories which are currently enabled.</p>
*/
public class BeaconTask extends TimerTask {
private ChatController chatController;
private ThreadStatusCallback callBackToController;
private String ThreadNickName = "MyBeacon";
private static final String THREAD_NICKNAME = "MyBeacon";
public BeaconTask(ChatController client, ThreadStatusCallback callback) {
this.callBackToController = callback;
this.chatController = client;
private final ChatController chatController;
private final ThreadStatusCallback callbackToController;
/**
* Creates one execution of the shared beacon timer.
*
* @param chatController controller providing preferences and the TX queue
* @param callbackToController callback used by the thread-status display
*/
public BeaconTask(
ChatController chatController,
ThreadStatusCallback callbackToController
) {
this.chatController = chatController;
this.callbackToController = callbackToController;
}
@Override
public void run() {
Thread.currentThread().setName("BeaconTask");
reportStatus(THREAD_NICKNAME, true, "initialized", false);
ThreadStateMessage threadStateMessage = new ThreadStateMessage(this.ThreadNickName, true, "initialized", false);
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
Thread.currentThread().setName("BeaconTask");
ChatMessage beaconMSG = new ChatMessage();
String replaceVariables = this.chatController.getChatPreferences().getBcn_beaconTextMainCat();
replaceVariables = replaceVariables.replaceAll("MYQRG", this.chatController.getChatPreferences().getMYQRGFirstCat().getValue());
replaceVariables = replaceVariables.replaceAll("MYCALL", this.chatController.getChatPreferences().getStn_loginCallSign());
replaceVariables = replaceVariables.replaceAll("MYLOCATOR", this.chatController.getChatPreferences().getStn_loginLocatorMainCat());
replaceVariables = replaceVariables.replaceAll("MYQTF", this.chatController.getChatPreferences().getActualQTF().getValue() + "");
replaceVariables = replaceVariables.replaceAll("SECONDQRG", this.chatController.getChatPreferences().getMYQRGSecondCat().getValue() + "");
beaconMSG.setMessageText(
"MSG|" + this.chatController.getChatPreferences().getLoginChatCategoryMain().getCategoryNumber() + "|0|" + replaceVariables + "|0|");
beaconMSG.setMessageDirectedToServer(true);
ChatMessage beaconMSG2 = new ChatMessage();
String replaceVariables2 = this.chatController.getChatPreferences().getBcn_beaconTextSecondCat();
replaceVariables2 = replaceVariables2.replaceAll("MYQRG", this.chatController.getChatPreferences().getMYQRGFirstCat().getValue());
replaceVariables2 = replaceVariables2.replaceAll("MYCALL", this.chatController.getChatPreferences().getStn_loginCallSign());
replaceVariables2 = replaceVariables2.replaceAll("MYLOCATOR", this.chatController.getChatPreferences().getStn_loginLocatorMainCat());
replaceVariables2 = replaceVariables2.replaceAll("MYQTF", this.chatController.getChatPreferences().getActualQTF().getValue() + "");
replaceVariables2 = replaceVariables2.replaceAll("SECONDQRG", this.chatController.getChatPreferences().getMYQRGSecondCat().getValue() + "");
beaconMSG2.setMessageText(
"MSG|" + this.chatController.getChatPreferences().getLoginChatCategorySecond().getCategoryNumber() + "|0|" + replaceVariables + "|0|");
beaconMSG2.setMessageDirectedToServer(true);
/**
* beacon 1st Chatcategory
*/
if (this.chatController.getChatPreferences().isBcn_beaconsEnabledMainCat() ) {
System.out.println(new Utils4KST().time_generateCurrentMMDDhhmmTimeString()
+ " [BeaconTask, Info]: Sending CQ: " + beaconMSG.getMessageText());
this.chatController.getMessageTXBus().add(beaconMSG);
threadStateMessage = new ThreadStateMessage(this.ThreadNickName + " 1", true, "on", false);
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
} else {
threadStateMessage = new ThreadStateMessage(this.ThreadNickName + " 1", false, "off", false);
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
}
/**
* beacon 2nd Chatcategory
*/
if (this.chatController.getChatPreferences().isLoginToSecondChatEnabled()) { //only send if 2nd cat enabled
if (this.chatController.getChatPreferences().isBcn_beaconsEnabledSecondCat()) {
beaconMSG2.setMessageText(
"MSG|" + this.chatController.getChatPreferences().getLoginChatCategorySecond().getCategoryNumber() + "|0|" + replaceVariables2 + "|0|");
beaconMSG2.setMessageDirectedToServer(true);
System.out.println(new Utils4KST().time_generateCurrentMMDDhhmmTimeString()
+ " [BeaconTask, Info]: Sending CQ 2nd Cat: " + beaconMSG2.getMessageText());
this.chatController.getMessageTXBus().add(beaconMSG2);
threadStateMessage = new ThreadStateMessage(this.ThreadNickName + " 2", true, "on", false);
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
} else {
threadStateMessage = new ThreadStateMessage(this.ThreadNickName + " 2", false, "off", false);
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
}
}
MessageVariableResolver variableResolver =
new MessageVariableResolver(chatController.getChatPreferences());
sendMainCategoryBeacon(variableResolver);
sendSecondCategoryBeacon(variableResolver);
}
}
/**
* Sends the main-category beacon if it is currently enabled.
*/
private void sendMainCategoryBeacon(MessageVariableResolver variableResolver) {
if (!chatController.getChatPreferences().isBcn_beaconsEnabledMainCat()) {
reportStatus(THREAD_NICKNAME + " 1", false, "off", false);
return;
}
String resolvedText = variableResolver.resolveGlobalVariables(
chatController.getChatPreferences().getBcn_beaconTextMainCat()
);
ChatMessage beaconMessage = buildBeaconMessage(
chatController.getChatPreferences()
.getLoginChatCategoryMain()
.getCategoryNumber(),
resolvedText,
"main category"
);
if (beaconMessage == null) {
reportStatus(THREAD_NICKNAME + " 1", false, "invalid text", true);
return;
}
System.out.println(
new Utils4KST().time_generateCurrentMMDDhhmmTimeString()
+ " [BeaconTask, Info]: Sending main-category CQ: "
+ beaconMessage.getMessageText()
);
chatController.getMessageTXBus().add(beaconMessage);
reportStatus(THREAD_NICKNAME + " 1", true, "on", false);
}
/**
* Sends the second-category beacon if the second login and its beacon are
* currently enabled.
*/
private void sendSecondCategoryBeacon(
MessageVariableResolver variableResolver
) {
if (!chatController.getChatPreferences().isLoginToSecondChatEnabled()
|| !chatController.getChatPreferences()
.isBcn_beaconsEnabledSecondCat()) {
reportStatus(THREAD_NICKNAME + " 2", false, "off", false);
return;
}
String resolvedText = variableResolver.resolveGlobalVariables(
chatController.getChatPreferences().getBcn_beaconTextSecondCat()
);
ChatMessage beaconMessage = buildBeaconMessage(
chatController.getChatPreferences()
.getLoginChatCategorySecond()
.getCategoryNumber(),
resolvedText,
"second category"
);
if (beaconMessage == null) {
reportStatus(THREAD_NICKNAME + " 2", false, "invalid text", true);
return;
}
System.out.println(
new Utils4KST().time_generateCurrentMMDDhhmmTimeString()
+ " [BeaconTask, Info]: Sending second-category CQ: "
+ beaconMessage.getMessageText()
);
chatController.getMessageTXBus().add(beaconMessage);
reportStatus(THREAD_NICKNAME + " 2", true, "on", false);
}
/**
* Builds the server-directed message after validating the resolved payload.
*
* <p>The resolved text is checked rather than only the configured template
* because inserted values can increase the final message length.</p>
*
* @param categoryNumber ON4KST category number
* @param resolvedText fully resolved beacon payload
* @param categoryDescription text used in diagnostic output
* @return prepared message, or {@code null} if the payload is invalid
*/
private ChatMessage buildBeaconMessage(
int categoryNumber,
String resolvedText,
String categoryDescription
) {
if (resolvedText == null
|| resolvedText.length() > ChatController.MAX_BEACON_TEXT_LENGTH) {
int actualLength = resolvedText == null ? 0 : resolvedText.length();
System.out.println(
"[BeaconTask, Warning]: Beacon for "
+ categoryDescription
+ " was not sent because the resolved text contains "
+ actualLength
+ " characters; maximum is "
+ ChatController.MAX_BEACON_TEXT_LENGTH
+ "."
);
return null;
}
ChatMessage beaconMessage = new ChatMessage();
beaconMessage.setMessageText(
"MSG|" + categoryNumber + "|0|" + resolvedText + "|0|"
);
beaconMessage.setMessageDirectedToServer(true);
return beaconMessage;
}
/**
* Forwards one state update to the existing thread-status display.
*/
private void reportStatus(
String threadName,
boolean running,
String information,
boolean criticalState
) {
ThreadStateMessage stateMessage = new ThreadStateMessage(
threadName,
running,
information,
criticalState
);
callbackToController.onThreadStatus(THREAD_NICKNAME, stateMessage);
}
}
@@ -59,6 +59,10 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
private static final boolean DEBUG_BAND_UPGRADE_HINT = true; //for new band hint
public static final int MIN_BEACON_INTERVAL_MINUTES = 1;
public static final int MAX_BEACON_TEXT_LENGTH = 120;
private static final long INITIAL_BEACON_DELAY_MILLIS = 10_000L;
private PstRotatorClient rotatorClient;
private Consumer<Double> viewRotorCallback;
@@ -708,8 +712,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
// writeThread.interrupt();
// readThread.interrupt();
beaconTimer.purge();
beaconTimer.cancel();
stopBeaconTimer();
ASQueryTimer.purge();
ASQueryTimer.cancel();
socketCheckTimer.purge();
@@ -2239,6 +2242,78 @@ private ObservableList<String>
this.dbHandler = dbHandler;
}
/**
* Starts the shared beacon timer with the interval currently stored in the
* preferences.
*
* <p>Both chat categories deliberately use the same timer. The task checks the
* individual enable flag and text for each category every time it runs.</p>
*
* @param initialDelayMillis delay before the next beacon run
*/
private synchronized void scheduleBeaconTimer(long initialDelayMillis) {
stopBeaconTimer();
int configuredInterval =
chatPreferences.getBcn_beaconIntervalInMinutesMainCat();
int effectiveInterval =
Math.max(MIN_BEACON_INTERVAL_MINUTES, configuredInterval);
/*
* Keep both legacy XML values synchronized. The second value remains in the
* configuration for backward compatibility, but no longer represents an
* independent timer.
*/
chatPreferences.setBcn_beaconIntervalInMinutesMainCat(effectiveInterval);
chatPreferences.setBcn_beaconIntervalInMinutesSecondCat(effectiveInterval);
long intervalMillis = TimeUnit.MINUTES.toMillis(effectiveInterval);
long safeInitialDelay = Math.max(0L, initialDelayMillis);
beaconTimer = new Timer("BeaconTimer");
beaconTimer.schedule(
new BeaconTask(this, this),
safeInitialDelay,
intervalMillis
);
System.out.println("[ChatController, Info]: Shared beacon timer scheduled every "
+ effectiveInterval + " minute(s).");
}
/**
* Applies a changed beacon interval while the chat connection is running.
*
* <p>The countdown starts again with the new interval. No beacon is sent
* immediately merely because the setting was changed.</p>
*/
public synchronized void restartBeaconTimer() {
if (!isConnectedAndLoggedIn()) {
return;
}
long intervalMillis = TimeUnit.MINUTES.toMillis(
Math.max(
MIN_BEACON_INTERVAL_MINUTES,
chatPreferences.getBcn_beaconIntervalInMinutesMainCat()
)
);
scheduleBeaconTimer(intervalMillis);
}
/**
* Stops the shared beacon timer if it is currently running.
*/
private synchronized void stopBeaconTimer() {
if (beaconTimer == null) {
return;
}
beaconTimer.cancel();
beaconTimer.purge();
beaconTimer = null;
}
/**
* execute is the main entry point where the application starts.
* @throws InterruptedException
@@ -2338,11 +2413,7 @@ private ObservableList<String>
* The CQ-beacon-Task will be executed every time but checks for itself whether
* CQ messages are enabled or not
*/
// Timer beaconTimer;
beaconTimer = new Timer();
beaconTimer.schedule(new BeaconTask(this, this), 10000,
this.getChatPreferences().getBcn_beaconIntervalInMinutesMainCat() * 60000);
// 60000 * intervalInMinutes = IntervalInMillis
scheduleBeaconTimer(INITIAL_BEACON_DELAY_MILLIS);
/**
* The AS querier task will be executed every time but checks for itself whether
@@ -0,0 +1,154 @@
package kst4contest.controller;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import kst4contest.model.AirPlane;
import kst4contest.model.AirPlaneReflectionInfo;
import kst4contest.model.ChatMember;
import kst4contest.model.ChatPreferences;
/**
* Resolves variables used in operator messages, shortcuts, snippets and
* beacons.
*
* <p>Global variables only depend on the local station configuration and can
* therefore be used in every message context. Station variables additionally
* require a selected remote station. Keeping both groups in one resolver
* prevents the send field and the beacon tasks from implementing different
* replacement rules.</p>
*/
public final class MessageVariableResolver {
private static final int SHORT_VALUE_LENGTH = 7;
private final ChatPreferences chatPreferences;
/**
* Creates a resolver backed by the live application preferences.
*
* @param chatPreferences preferences that provide current station values
*/
public MessageVariableResolver(ChatPreferences chatPreferences) {
this.chatPreferences = Objects.requireNonNull(chatPreferences, "chatPreferences");
}
/**
* Resolves variables which do not require a selected remote station.
*
* <p>The replacement is literal rather than regular-expression based.
* Callsigns, locators and frequencies are data, not regular expressions.</p>
*
* @param template text that may contain variables
* @return text with all available global variables resolved, or {@code null}
* when the supplied template is {@code null}
*/
public String resolveGlobalVariables(String template) {
if (template == null) {
return null;
}
String primaryFrequency = valueOrEmpty(chatPreferences.getMYQRGFirstCat().getValue());
String secondaryFrequency = valueOrEmpty(chatPreferences.getMYQRGSecondCat().getValue());
String ownLocator = valueOrEmpty(chatPreferences.getStn_loginLocatorMainCat());
String resolvedText = template;
resolvedText = resolvedText.replace("MYQRGSHORT", abbreviate(primaryFrequency));
resolvedText = resolvedText.replace("MYQRG", primaryFrequency);
resolvedText = resolvedText.replace("SECONDQRG", secondaryFrequency);
resolvedText = resolvedText.replace("MYLOCATORSHORT", abbreviateLocator(ownLocator));
resolvedText = resolvedText.replace("MYLOCATOR", ownLocator);
resolvedText = resolvedText.replace("MYCALL", valueOrEmpty(chatPreferences.getStn_loginCallSign()));
resolvedText = resolvedText.replace("MYQTF", formatHeading(chatPreferences.getActualQTF().getValue().doubleValue()));
return resolvedText;
}
/**
* Resolves global variables and variables derived from a selected station.
*
* <p>If no station is selected, station-specific placeholders remain visible.
* This is intentional: silently removing {@code QRZNAME}, {@code FIRSTAP} or
* {@code SECONDAP} could create a plausible-looking but incomplete message.</p>
*
* @param template text that may contain variables
* @param selectedStation currently selected remote station, may be {@code null}
* @return resolved message text
*/
public String resolveForSelectedStation(String template, ChatMember selectedStation) {
String resolvedText = resolveGlobalVariables(template);
if (resolvedText == null || selectedStation == null) {
return resolvedText;
}
resolvedText = resolvedText.replace("QRZNAME", resolveStationName(selectedStation));
resolvedText = resolvedText.replace("FIRSTAP", resolveFirstAirPlane(selectedStation));
resolvedText = resolvedText.replace("SECONDAP", resolveSecondAirPlane(selectedStation));
return resolvedText;
}
private String resolveStationName(ChatMember selectedStation) {
String stationName = valueOrEmpty(selectedStation.getName()).trim();
if (!stationName.isEmpty()) {
return stationName;
}
return valueOrEmpty(selectedStation.getCallSign());
}
private String resolveFirstAirPlane(ChatMember selectedStation) {
List<AirPlane> risingAirPlanes = getRisingAirPlanes(selectedStation);
if (risingAirPlanes.isEmpty()) {
return "no ap available";
}
AirPlane firstAirPlane = risingAirPlanes.get(0);
return "a " + firstAirPlane.getPotencialDescriptionAsWord()
+ " in " + firstAirPlane.getArrivingDurationMinutes() + " min";
}
private String resolveSecondAirPlane(ChatMember selectedStation) {
List<AirPlane> risingAirPlanes = getRisingAirPlanes(selectedStation);
if (risingAirPlanes.size() < 2) {
return "";
}
AirPlane secondAirPlane = risingAirPlanes.get(1);
return "Next " + secondAirPlane.getPotencialDescriptionAsWord()
+ " in " + secondAirPlane.getArrivingDurationMinutes() + " min";
}
private List<AirPlane> getRisingAirPlanes(ChatMember selectedStation) {
AirPlaneReflectionInfo reflectionInfo = selectedStation.getAirPlaneReflectInfo();
if (reflectionInfo == null || reflectionInfo.getRisingAirplanes() == null) {
return List.of();
}
return reflectionInfo.getRisingAirplanes();
}
private String abbreviate(String value) {
return value.substring(0, Math.min(value.length(), SHORT_VALUE_LENGTH));
}
private String abbreviateLocator(String locator) {
return locator.substring(0, Math.min(locator.length(), 4));
}
private String formatHeading(double headingDegrees) {
if (!Double.isFinite(headingDegrees)) {
return "";
}
return BigDecimal.valueOf(headingDegrees).stripTrailingZeros().toPlainString();
}
private String valueOrEmpty(String value) {
return value == null ? "" : value;
}
}
@@ -14,8 +14,8 @@ import java.util.TimerTask;
* The task will be runned out of the singleton ChatController instance in an
* intervall as specified by the Chatpreferences-instance (typically as
* configured in the xml file.
*
*
*
*
* @author prakt
*
*/
@@ -34,22 +34,20 @@ public class ScoreboardUpdateTask extends TimerTask {
Thread.currentThread().setName("BeaconTask");
ChatMessage beaconMSG = new ChatMessage();
String replaceVariables = this.chatController.getChatPreferences().getBcn_beaconTextMainCat();
// replaceVariables = bcn_beaconText;
replaceVariables = replaceVariables.replaceAll("MYQRG", this.chatController.getChatPreferences().getMYQRGFirstCat().getValue());
replaceVariables = replaceVariables.replaceAll("MYCALL", this.chatController.getChatPreferences().getStn_loginCallSign());
replaceVariables = replaceVariables.replaceAll("MYLOCATOR", this.chatController.getChatPreferences().getStn_loginLocatorMainCat());
replaceVariables = replaceVariables.replaceAll("MYQTF", this.chatController.getChatPreferences().getActualQTF().getValue() + "");
MessageVariableResolver variableResolver =
new MessageVariableResolver(this.chatController.getChatPreferences());
String replaceVariables = variableResolver.resolveGlobalVariables(
this.chatController.getChatPreferences().getBcn_beaconTextMainCat()
);
beaconMSG.setMessageText(
"MSG|" + this.chatController.getChatPreferences().getLoginChatCategoryMain().getCategoryNumber() + "|0|" + replaceVariables + "|0|");
beaconMSG.setMessageDirectedToServer(true);
// System.out.println("########### " + replaceVariables);
if (this.chatController.getChatPreferences().isBcn_beaconsEnabledMainCat() ) {
System.out.println(new Utils4KST().time_generateCurrentMMDDhhmmTimeString()
@@ -58,8 +56,8 @@ public class ScoreboardUpdateTask extends TimerTask {
} else {
//do nothing, CQ is disabled
}
}
}
}
@@ -0,0 +1,112 @@
package kst4contest.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import kst4contest.controller.MessageVariableResolver;
import kst4contest.model.AirPlane;
import kst4contest.model.AirPlaneReflectionInfo;
import kst4contest.model.ChatMember;
import kst4contest.model.ChatPreferences;
class MessageVariableResolverTest {
private ChatPreferences chatPreferences;
private MessageVariableResolver resolver;
@BeforeEach
void setUp() {
chatPreferences = mock(ChatPreferences.class);
when(chatPreferences.getMYQRGFirstCat()).thenReturn(new SimpleStringProperty("144.388.03"));
when(chatPreferences.getMYQRGSecondCat()).thenReturn(new SimpleStringProperty("432.088.00"));
when(chatPreferences.getStn_loginLocatorMainCat()).thenReturn("JO51IJ");
when(chatPreferences.getStn_loginCallSign()).thenReturn("DO5AMF");
when(chatPreferences.getActualQTF()).thenReturn(new SimpleDoubleProperty(135.0));
resolver = new MessageVariableResolver(chatPreferences);
}
@Test
void resolvesAllGlobalVariablesInEveryMessageContext() {
String template = "MYCALL MYQRGSHORT MYQRG SECONDQRG MYLOCATORSHORT MYLOCATOR MYQTF";
assertEquals(
"DO5AMF 144.388 144.388.03 432.088.00 JO51 JO51IJ 135",
resolver.resolveGlobalVariables(template)
);
}
@Test
void shortValuesRemainSafeWhenTheConfiguredValueIsShorterThanExpected() {
when(chatPreferences.getMYQRGFirstCat()).thenReturn(new SimpleStringProperty("144"));
when(chatPreferences.getStn_loginLocatorMainCat()).thenReturn("JO5");
assertEquals("144 JO5", resolver.resolveGlobalVariables("MYQRGSHORT MYLOCATORSHORT"));
}
@Test
void resolvesSelectedStationNameAndTheFirstTwoAirPlanes() {
ChatMember selectedStation = new ChatMember();
selectedStation.setCallSign("DL0TEST");
selectedStation.setName("Test Operator");
AirPlane firstAirPlane = new AirPlane();
firstAirPlane.setPotential(100);
firstAirPlane.setArrivingDurationMinutes(1);
AirPlane secondAirPlane = new AirPlane();
secondAirPlane.setPotential(75);
secondAirPlane.setArrivingDurationMinutes(9);
AirPlaneReflectionInfo reflectionInfo = new AirPlaneReflectionInfo();
reflectionInfo.setRisingAirplanes(FXCollections.observableArrayList(firstAirPlane, secondAirPlane));
selectedStation.setAirPlaneReflectInfo(reflectionInfo);
assertEquals(
"Hi Test Operator, a very big AP in 1 min; Next big AP in 9 min",
resolver.resolveForSelectedStation(
"Hi QRZNAME, FIRSTAP; SECONDAP",
selectedStation
)
);
}
@Test
void usesCallsignWhenTheSelectedStationHasNoName() {
ChatMember selectedStation = new ChatMember();
selectedStation.setCallSign("DL0TEST");
selectedStation.setName(" ");
assertEquals(
"Hi DL0TEST",
resolver.resolveForSelectedStation("Hi QRZNAME", selectedStation)
);
}
@Test
void keepsStationVariablesVisibleWhenNoStationIsSelected() {
assertEquals(
"QRZNAME FIRSTAP SECONDAP",
resolver.resolveForSelectedStation("QRZNAME FIRSTAP SECONDAP", null)
);
}
@Test
void returnsUsefulFallbacksWhenNoAirPlaneIsAvailable() {
ChatMember selectedStation = new ChatMember();
selectedStation.setCallSign("DL0TEST");
assertEquals(
"no ap available ",
resolver.resolveForSelectedStation("FIRSTAP SECONDAP", selectedStation)
);
}
}
File diff suppressed because it is too large Load Diff
+2
View File
@@ -10,6 +10,8 @@ module praktiKST {
requires java.net.http;
requires java.desktop;
requires jdk.crypto.ec;
requires org.junit.jupiter.api;
requires org.mockito;
exports kst4contest.controller.interfaces;
exports kst4contest.controller;
exports kst4contest.locatorUtils;