Merge branch 'feat/chat-tests' into 'main'

Feat/chat tests

See merge request cs108-fs26/Gruppe-13!161
This commit was merged in pull request #317.
This commit is contained in:
Mathis Ginkel
2026-04-26 22:52:30 +00:00
10 changed files with 236 additions and 238 deletions
@@ -0,0 +1,13 @@
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
import java.util.List;
/** Interface used to create test instances of the ChatClient */
public interface ChatClientInterface {
List<Message> getMessages();
void sendMessage(Message message);
List<String> getUsers();
}
@@ -33,9 +33,9 @@ public class ChatController {
private volatile String username;
private final ClientService clientService;
private ClientService clientService;
private final ChatClient chatClient;
private final ChatClientInterface chatClient;
public ChatBoxController getChatBoxController() {
return chatBoxController;
@@ -112,6 +112,39 @@ public class ChatController {
REFRESH_TIME);
}
/**
* Another constructor to give the ChatClient directly instead of the Client Service used only
* for test purposes
*
* @param username
*/
public ChatController(String username, ChatClientInterface chatClient) {
this.username = username;
this.chatClient = chatClient;
chatModelMap = new LinkedHashMap<>();
localUserList = new ArrayList<>();
this.chatBoxController = new ChatBoxController(username, this);
this.logger = LogManager.getLogger(ChatController.class);
this.serverEventListener = this::handleServerEvent;
this.activeChatControllers = new HashMap<>();
this.timer = new Timer(true);
timer.schedule(
new TimerTask() {
@Override
public void run() {
try {
receiveMessage();
checkWhisperUsers();
} catch (RuntimeException e) {
logger.warn("Chat refresh failed: {}", e.getMessage());
}
}
},
0,
REFRESH_TIME);
}
private void registerAsActiveController(ClientService clientService) {
synchronized (ACTIVE_CONTROLLERS) {
ChatController oldController = ACTIVE_CONTROLLERS.get(clientService);
@@ -2,8 +2,6 @@ package ch.unibas.dmi.dbis.cs108.casono.client.chat;
import java.util.ArrayList;
import java.util.function.Consumer;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
/**
* ChatModel, stores the data for a specific chat
@@ -12,7 +10,7 @@ import javafx.beans.property.SimpleIntegerProperty;
*/
public class ChatModel {
private ArrayList<Consumer<Message>> listeners = new ArrayList<>();
private final ArrayList<Consumer<Message>> listeners = new ArrayList<>();
public ArrayList<Message> messages;
@@ -24,8 +22,6 @@ public class ChatModel {
/** The person to send the message to If the chat is a whisper chat */
private String target;
private final IntegerProperty count;
public int lobbyId;
/**
@@ -37,10 +33,9 @@ public class ChatModel {
* @param target The username of the whisper recipient, or null for other chat types.
*/
public ChatModel(ChatType chattype, String username, int lobbyId, String target) {
this.messages = new ArrayList<Message>();
this.messages = new ArrayList<>();
this.chattype = chattype;
this.username = username;
this.count = new SimpleIntegerProperty(0);
this.lobbyId = lobbyId;
this.target = target;
}
@@ -62,7 +57,7 @@ public class ChatModel {
*/
public synchronized void addMessage(Message msg) {
messages.add(msg);
listeners.stream().forEach((l) -> l.accept(messages.getLast()));
listeners.forEach((l) -> l.accept(messages.getLast()));
}
/**
@@ -15,8 +15,8 @@ public class Message {
private final String message;
public String sender;
public String timestamp;
public int lobbyId = 0;
public String target = null;
public int lobbyId;
public String target;
/**
* Constructs a Message with a provided timestamp. Typically used when reconstructing messages
@@ -100,7 +100,7 @@ public class Message {
* @return A formatted string containing all message attributes for server transmission.
*/
public String toArgsString() {
String gameIdString = "";
String gameIdString;
if (lobbyId >= 0) {
gameIdString = " GAME=" + lobbyId;
} else {
@@ -1,5 +1,6 @@
package ch.unibas.dmi.dbis.cs108.casono.client.network;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatClientInterface;
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
import java.util.ArrayList;
@@ -12,7 +13,7 @@ import org.apache.logging.log4j.Logger;
* from the server. It uses the ClientService to send commands and receive responses from the
* server.
*/
public class ChatClient {
public class ChatClient implements ChatClientInterface {
private final ClientService clientService;
private final Logger logger;
@@ -195,8 +195,7 @@ public class ChatBoxController {
() -> {
try {
ChatViewController chatViewController =
new ChatViewController(
this.chatController, chatModel, this);
new ChatViewController(this.chatController, chatModel, this);
ChatKey chatKey;
if (chatType.equals(ChatType.WHISPER)) {
@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<!--
Dieses Fenster wird später mit den Serveranfragen verknüpft,
um Nachrichten von Mitspielern und Systemmeldungen in Echtzeit anzuzeigen.
Der Chat unterscheidet intern zwischen:
- Player-to-Player (Privater 2-Personen-Chat)
- Lobby-Chat (Aktueller Raum)
- Globaler Chat (Gesamter Server)
Features, die später implementiert werden sollen:
- Schicke Chat-Bubbles mit Namen und Uhrzeit.
-->
<VBox xmlns="http://javafx.com/javafx/21"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatViewController"
alignment="CENTER"
stylesheets="@chatui.css">
<!-- Innenabstand: Schafft oben, rechts und unten 30 Pixel Platz, links nur 10 Pixel (asymmetrisch) -->
<padding>
<Insets top="30" right="30" bottom="30" left="10"/>
</padding>
<children>
<VBox VBox.vgrow="ALWAYS"
styleClass="chat-box"
spacing="10"
maxWidth="Infinity">
<children>
<Label text="== CHAT ==" styleClass="chat-header" alignment="CENTER" maxWidth="Infinity"/>
<Region minHeight="4" styleClass="chat-separator"/>
<!-- Chatnachrichten werden hier hinzugefügt -->
<ScrollPane fx:id="chatScrollPane" fitToWidth="true" vbarPolicy="AS_NEEDED" hbarPolicy="NEVER" VBox.vgrow="ALWAYS" styleClass="chat-scroll-pane">
<content>
<VBox fx:id="chatVBox" spacing="10" styleClass="chat-VBox"/>
</content>
</ScrollPane>
<!-- Eingabebereich -->
<HBox spacing="10">
<!-- TODO: Größe des TextFields dynamisch anpassen -->
<TextField fx:id="inputField" HBox.hgrow="ALWAYS" promptText="Nachricht eingeben..." styleClass="gray-input-field" onAction="#sendMessage"/>
<Button fx:id="sendButton" text="SENDEN" onAction="#sendMessage" styleClass="yellow-button"/>
<Button fx:id="refreshButton" text="NEU" onAction="#showMessage" styleClass="yellow-button"/>
</HBox>
</children>
</VBox>
</children>
</VBox>
@@ -1,155 +0,0 @@
.chat-box {
-fx-background-color: #0d9e3b;
-fx-background-radius: 58;
-fx-border-radius: 50;
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
-fx-border-width: 12;
-fx-padding: 20;
-fx-effect: dropshadow(one-pass-box, rgba(0,0,0,1), 0, 0, 15, 15);
}
.chat-header {
-fx-text-fill: #ffffff;
-fx-font-size: 22px;
-fx-font-weight: bold;
-fx-padding: 0 0 10 0;
-fx-effect: dropshadow(one-pass-box, #000000, 0, 0, 3, 3);
}
.chat-separator {
-fx-background-color: #ffffff;
-fx-min-height: 4px;
-fx-effect: dropshadow(one-pass-box, #000000, 0, 0, 3, 3);
}
.black-input-field {
-fx-background-color: #1a1a1a;
-fx-text-fill: #ffffff;
-fx-font-family: "Courier New";
-fx-font-weight: bold;
-fx-background-radius: 8;
-fx-border-radius: 8;
-fx-border-color: #444444;
-fx-border-width: 2;
-fx-padding: 5 12;
}
.black-input-field:hover {
-fx-translate-y: -3;
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
}
.black-input-field:focused {
-fx-border-color: #ffffff;
-fx-background-color: #000000;
}
.gray-input-field {
-fx-background-color: #333333;
-fx-text-fill: #ffffff;
-fx-font-family: "Courier New";
-fx-font-weight: bold;
-fx-background-radius: 8;
-fx-border-radius: 8;
-fx-border-color: #666666;
-fx-color-color: #cccccc;
-fx-border-width: 2;
-fx-padding: 5 12;
}
.gray-input-field:hover {
-fx-translate-y: -3;
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
}
.gray-input-field:focused {
-fx-border-color: #ffffff;
-fx-background-color: #333333;
}
.yellow-button {
-fx-background-color: #b8860b;
-fx-border-color: #ffd700;
-fx-text-fill: #ffffff;
}
.red-button {
-fx-background-color: #8b0000;
-fx-border-color: #ff4444;
-fx-text-fill: #ffffff;
}
.gray-button {
-fx-background-color: #333333;
-fx-border-color: #666666;
-fx-text-fill: #cccccc;
}
.gray-button, .yellow-button, .red-button {
-fx-background-radius: 12;
-fx-border-radius: 12;
-fx-font-family: "Courier New";
-fx-font-weight: bold;
-fx-border-width: 2;
-fx-padding: 6 15;
-fx-cursor: hand;
}
.gray-button:hover, .yellow-button:hover, .red-button:hover {
-fx-translate-y: -3;
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
}
.chat-scroll-pane {
-fx-background-color: transparent;
-fx-background: transparent;
-fx-border-color: transparent;
}
.chat-VBox {
-fx-background-color: transparent;
-fx-background: transparent;
}
.scroll-pane {
-fx-background-color: transparent;
-fx-border-color: transparent;
}
.scroll-pane .scroll-bar:vertical {
-fx-background-color: transparent;
}
.scroll-pane .scroll-bar .track {
-fx-background-color: #5c3d10;
-fx-background-insets: 0;
-fx-background-radius: 5;
}
.scroll-pane .scroll-bar .thumb {
-fx-background-color: #3d260a;
-fx-background-insets: 0;
-fx-background-radius: 5;
-fx-pref-width: 6;
-fx-pref-height: 6;
-fx-padding: 0;
}
.scroll-pane .scroll-bar:horizontal {
-fx-background-color: transparent;
}
.scroll-pane .scroll-bar:horizontal .track {
-fx-background-color: #5c3d10;
-fx-background-radius: 5;
}
.scroll-pane .scroll-bar:horizontal .thumb {
-fx-background-color: #3d260a;
-fx-background-radius: 5;
-fx-pref-height: 6;
}
.scroll-pane .scroll-bar:horizontal .thumb:hover {
-fx-pref-height: 10;
}
@@ -0,0 +1,41 @@
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class ChatClientTest implements ChatClientInterface {
private ChatClientTest otherChatClient;
public final Queue<Message> messageQueue;
public ChatClientTest() {
this.messageQueue = new LinkedList<>();
}
public ChatClientTest(ChatClientTest otherChatClient) {
this.otherChatClient = otherChatClient;
this.messageQueue = new LinkedList<>();
}
@Override
public void sendMessage(Message message) {
otherChatClient.messageQueue.add(message);
}
@Override
public List<Message> getMessages() {
List<Message> messages = new LinkedList<>();
while (!messageQueue.isEmpty()) {
Message message = messageQueue.poll();
messages.add(message);
}
return messages;
}
@Override
public List<String> getUsers() {
return List.of();
}
}
@@ -1,21 +1,151 @@
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import java.util.ArrayList;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class ChatControllerTest {
ChatController chatController1;
ChatController chatController2;
String name1 = "testperson1";
String name2 = "testperson1";
@BeforeEach
public void setup() {}
public void setup() {
ChatClientTest receivingChatClient = new ChatClientTest();
ChatClientTest sendingChatClient = new ChatClientTest(receivingChatClient);
this.chatController1 = new ChatController(name1, sendingChatClient);
this.chatController2 = new ChatController(name2, receivingChatClient);
}
@AfterEach
public void teardown() {}
/*
* @Test
* public void testChatController() {
* Client client = new Client("user1");
* client.startApplication();
* }
*/
@Test
public void sendGlobalMessage() {
ChatModel global = new ChatModel(ChatType.GLOBAL, name2, -1, null);
chatController2.getChatModelMap().put(new ChatController.ChatKey(ChatType.GLOBAL), global);
Message msg1 = new Message(ChatType.GLOBAL, -1, name1, null, "Hallo Welt");
Message msg2 = new Message(ChatType.GLOBAL, -1, name1, null, "$$Hallo$$Welt$$");
Message msg3 = new Message(ChatType.GLOBAL, -1, name1, null, "_*'=(&%$/§&%");
Message msg4 = new Message(ChatType.LOBBY, -1, name1, null, "Guten Tag");
Message msg5 = new Message(ChatType.LOBBY, -1, name1, null, "$/&)$)(&=)$=");
Message msg6 = new Message(ChatType.GLOBAL, -1, name1, null, "NAME=ANDERER_NUTZER");
Message msg7 = new Message(ChatType.GLOBAL, -1, name1, null, "________");
ArrayList<Message> messages = new ArrayList<>();
messages.add(msg1);
messages.add(msg2);
messages.add(msg3);
messages.add(msg4);
messages.add(msg5);
messages.add(msg6);
messages.add(msg7);
chatController1.onSendToNetwork(msg1);
chatController1.onSendToNetwork(msg2);
chatController1.onSendToNetwork(msg3);
chatController1.onSendToNetwork(msg4);
chatController1.onSendToNetwork(msg5);
chatController1.onSendToNetwork(msg6);
chatController1.onSendToNetwork(msg7);
chatController2.receiveMessage();
assertEquals(5, global.messages.size());
assertEquals(messages.get(0), global.messages.get(0));
assertEquals(messages.get(1), global.messages.get(1));
assertEquals(messages.get(2), global.messages.get(2));
assertNotEquals(messages.get(3), global.messages.get(3));
assertNotEquals(messages.get(4), global.messages.get(4));
assertEquals(messages.get(5), global.messages.get(3));
assertEquals(messages.get(6), global.messages.get(4));
}
@Test
public void sendLobbyMessage() {
ChatModel lobby = new ChatModel(ChatType.LOBBY, name2, 1, null);
chatController2.getChatModelMap().put(new ChatController.ChatKey(ChatType.LOBBY), lobby);
Message msg1 = new Message(ChatType.LOBBY, 1, name1, null, "Hallo Welt");
Message msg2 = new Message(ChatType.LOBBY, 1, name1, null, "$$Hallo$$Welt$$");
Message msg3 = new Message(ChatType.LOBBY, 1, name1, null, "_*'=(&%$/§&%");
Message msg4 = new Message(ChatType.LOBBY, 4, name1, null, "Guten Tag");
Message msg5 = new Message(ChatType.LOBBY, 5, name1, null, "$/&)$)(&=)$=");
Message msg6 = new Message(ChatType.LOBBY, 1, name1, null, "NAME=ANDERER_NUTZER");
Message msg7 = new Message(ChatType.LOBBY, 1, name1, null, "________");
ArrayList<Message> messages = new ArrayList<>();
messages.add(msg1);
messages.add(msg2);
messages.add(msg3);
messages.add(msg4);
messages.add(msg5);
messages.add(msg6);
messages.add(msg7);
for (Message msg : messages) {
chatController1.onSendToNetwork(msg);
}
chatController2.receiveMessage();
assertEquals(5, lobby.messages.size());
assertEquals(messages.get(0), lobby.messages.get(0));
assertEquals(messages.get(1), lobby.messages.get(1));
assertEquals(messages.get(2), lobby.messages.get(2));
assertNotEquals(messages.get(3), lobby.messages.get(3));
assertNotEquals(messages.get(4), lobby.messages.get(4));
assertEquals(messages.get(5), lobby.messages.get(3));
assertEquals(messages.get(6), lobby.messages.get(4));
}
@Test
public void sendWhisperMessage() {
ChatModel whisper = new ChatModel(ChatType.WHISPER, name2, -1, name1);
chatController2
.getChatModelMap()
.put(new ChatController.ChatKey(ChatType.WHISPER, name1), whisper);
Message msg1 = new Message(ChatType.WHISPER, -1, name1, name2, "Hallo Welt");
Message msg2 = new Message(ChatType.WHISPER, -1, name1, name2, "$$Hallo$$Welt$$");
Message msg3 = new Message(ChatType.WHISPER, -1, name1, name2, "_*'=(&%$/§&%");
Message msg4 = new Message(ChatType.WHISPER, -1, name1, name2, "NAME=ANDERER_NUTZER");
Message msg5 = new Message(ChatType.WHISPER, -1, name1, name2, "________");
ArrayList<Message> messages = new ArrayList<>();
messages.add(msg1);
messages.add(msg2);
messages.add(msg3);
messages.add(msg4);
messages.add(msg5);
for (Message msg : messages) {
chatController1.onSendToNetwork(msg);
}
chatController2.receiveMessage();
assertEquals(5, whisper.messages.size());
assertEquals(messages.get(0), whisper.messages.get(0));
assertEquals(messages.get(1), whisper.messages.get(1));
assertEquals(messages.get(2), whisper.messages.get(2));
assertEquals(messages.get(3), whisper.messages.get(3));
assertEquals(messages.get(4), whisper.messages.get(4));
}
}