From c9bdb3e0cfc3394eabcc0763421313c3cee17bc7 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Tue, 11 Nov 2025 16:40:03 +0100
Subject: [PATCH 01/17] added env
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 6ac465db..244268f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
target/
/.idea/
+.env
From b9fe889904f1c27e3889905798899848ceb21917 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Tue, 11 Nov 2025 16:59:05 +0100
Subject: [PATCH 02/17] added chatview and dependencys
---
pom.xml | 14 ++++++++--
.../java/com/example/HelloController.java | 4 +++
src/main/java/com/example/HelloFX.java | 2 +-
src/main/resources/com/example/chat-view.fxml | 26 +++++++++++++++++++
.../resources/com/example/hello-view.fxml | 9 -------
5 files changed, 43 insertions(+), 12 deletions(-)
create mode 100644 src/main/resources/com/example/chat-view.fxml
delete mode 100644 src/main/resources/com/example/hello-view.fxml
diff --git a/pom.xml b/pom.xml
index c40f667e..25676a50 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,6 +45,16 @@
javafx-fxml
${javafx.version}
+
+ io.github.cdimascio
+ dotenv-java
+ 3.2.0
+
+
+ tools.jackson.core
+ jackson-databind
+ 3.0.1
+
@@ -55,7 +65,7 @@
com.example.HelloFX
-
+
javafx
true
@@ -65,4 +75,4 @@
-
+
\ No newline at end of file
diff --git a/src/main/java/com/example/HelloController.java b/src/main/java/com/example/HelloController.java
index fdd160a0..851d52a5 100644
--- a/src/main/java/com/example/HelloController.java
+++ b/src/main/java/com/example/HelloController.java
@@ -1,7 +1,9 @@
package com.example;
import javafx.fxml.FXML;
+import javafx.scene.control.Button;
import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
/**
* Controller layer: mediates between the view (FXML) and the model.
@@ -9,6 +11,8 @@
public class HelloController {
private final HelloModel model = new HelloModel();
+ public TextField inputTextField;
+ public Button sendButton;
@FXML
private Label messageLabel;
diff --git a/src/main/java/com/example/HelloFX.java b/src/main/java/com/example/HelloFX.java
index 96bdc5ca..2517ef4b 100644
--- a/src/main/java/com/example/HelloFX.java
+++ b/src/main/java/com/example/HelloFX.java
@@ -10,7 +10,7 @@ public class HelloFX extends Application {
@Override
public void start(Stage stage) throws Exception {
- FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("hello-view.fxml"));
+ FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("chat-view.fxml"));
Parent root = fxmlLoader.load();
Scene scene = new Scene(root, 640, 480);
stage.setTitle("Hello MVC");
diff --git a/src/main/resources/com/example/chat-view.fxml b/src/main/resources/com/example/chat-view.fxml
new file mode 100644
index 00000000..7afd7c84
--- /dev/null
+++ b/src/main/resources/com/example/chat-view.fxml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/com/example/hello-view.fxml b/src/main/resources/com/example/hello-view.fxml
deleted file mode 100644
index 20a7dc82..00000000
--- a/src/main/resources/com/example/hello-view.fxml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
From a1c06e9bbab2f15d8fcf3fd3c92fad5af8b88f76 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Tue, 11 Nov 2025 18:59:31 +0100
Subject: [PATCH 03/17] renamed project files
---
pom.xml | 2 +-
.../java/com/example/{HelloFX.java => ChatApplication.java} | 4 ++--
.../com/example/{HelloController.java => ChatController.java} | 4 ++--
src/main/java/com/example/{HelloModel.java => ChatModel.java} | 2 +-
src/main/resources/com/example/chat-view.fxml | 2 +-
5 files changed, 7 insertions(+), 7 deletions(-)
rename src/main/java/com/example/{HelloFX.java => ChatApplication.java} (76%)
rename src/main/java/com/example/{HelloController.java => ChatController.java} (85%)
rename src/main/java/com/example/{HelloModel.java => ChatModel.java} (94%)
diff --git a/pom.xml b/pom.xml
index 25676a50..f4540fe2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,7 +63,7 @@
javafx-maven-plugin
0.0.8
- com.example.HelloFX
+ com.example.ChatApplication
diff --git a/src/main/java/com/example/HelloFX.java b/src/main/java/com/example/ChatApplication.java
similarity index 76%
rename from src/main/java/com/example/HelloFX.java
rename to src/main/java/com/example/ChatApplication.java
index 2517ef4b..19cc8801 100644
--- a/src/main/java/com/example/HelloFX.java
+++ b/src/main/java/com/example/ChatApplication.java
@@ -6,11 +6,11 @@
import javafx.scene.Scene;
import javafx.stage.Stage;
-public class HelloFX extends Application {
+public class ChatApplication extends Application {
@Override
public void start(Stage stage) throws Exception {
- FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("chat-view.fxml"));
+ FXMLLoader fxmlLoader = new FXMLLoader(ChatApplication.class.getResource("chat-view.fxml"));
Parent root = fxmlLoader.load();
Scene scene = new Scene(root, 640, 480);
stage.setTitle("Hello MVC");
diff --git a/src/main/java/com/example/HelloController.java b/src/main/java/com/example/ChatController.java
similarity index 85%
rename from src/main/java/com/example/HelloController.java
rename to src/main/java/com/example/ChatController.java
index 851d52a5..257d9048 100644
--- a/src/main/java/com/example/HelloController.java
+++ b/src/main/java/com/example/ChatController.java
@@ -8,9 +8,9 @@
/**
* Controller layer: mediates between the view (FXML) and the model.
*/
-public class HelloController {
+public class ChatController {
- private final HelloModel model = new HelloModel();
+ private final ChatModel model = new ChatModel();
public TextField inputTextField;
public Button sendButton;
diff --git a/src/main/java/com/example/HelloModel.java b/src/main/java/com/example/ChatModel.java
similarity index 94%
rename from src/main/java/com/example/HelloModel.java
rename to src/main/java/com/example/ChatModel.java
index 385cfd10..4025371c 100644
--- a/src/main/java/com/example/HelloModel.java
+++ b/src/main/java/com/example/ChatModel.java
@@ -3,7 +3,7 @@
/**
* Model layer: encapsulates application data and business logic.
*/
-public class HelloModel {
+public class ChatModel {
/**
* Returns a greeting based on the current Java and JavaFX versions.
*/
diff --git a/src/main/resources/com/example/chat-view.fxml b/src/main/resources/com/example/chat-view.fxml
index 7afd7c84..95d286e9 100644
--- a/src/main/resources/com/example/chat-view.fxml
+++ b/src/main/resources/com/example/chat-view.fxml
@@ -8,7 +8,7 @@
-
+
From cca5d366ffa143fbd5183e459f7d9030ca7b4758 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Wed, 12 Nov 2025 12:21:48 +0100
Subject: [PATCH 04/17] added back ntfy files
---
src/main/java/com/example/ChatMessage.java | 4 ++
src/main/java/com/example/ChatModel.java | 18 ++---
src/main/java/com/example/NtfyConnection.java | 11 ++++
.../java/com/example/NtfyConnectionImpl.java | 65 +++++++++++++++++++
src/main/java/com/example/NtfyMessageDto.java | 7 ++
src/main/java/module-info.java | 4 ++
6 files changed, 98 insertions(+), 11 deletions(-)
create mode 100644 src/main/java/com/example/ChatMessage.java
create mode 100644 src/main/java/com/example/NtfyConnection.java
create mode 100644 src/main/java/com/example/NtfyConnectionImpl.java
create mode 100644 src/main/java/com/example/NtfyMessageDto.java
diff --git a/src/main/java/com/example/ChatMessage.java b/src/main/java/com/example/ChatMessage.java
new file mode 100644
index 00000000..652975f1
--- /dev/null
+++ b/src/main/java/com/example/ChatMessage.java
@@ -0,0 +1,4 @@
+package com.example;
+
+public record ChatMessage(String content, long timestamp) {
+}
diff --git a/src/main/java/com/example/ChatModel.java b/src/main/java/com/example/ChatModel.java
index 4025371c..0ee3d488 100644
--- a/src/main/java/com/example/ChatModel.java
+++ b/src/main/java/com/example/ChatModel.java
@@ -1,15 +1,11 @@
package com.example;
-/**
- * Model layer: encapsulates application data and business logic.
- */
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
public class ChatModel {
- /**
- * Returns a greeting based on the current Java and JavaFX versions.
- */
- public String getGreeting() {
- String javaVersion = System.getProperty("java.version");
- String javafxVersion = System.getProperty("javafx.version");
- return "Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".";
- }
+ private final ObservableList messages = FXCollections.observableArrayList();
+
+
}
diff --git a/src/main/java/com/example/NtfyConnection.java b/src/main/java/com/example/NtfyConnection.java
new file mode 100644
index 00000000..c83095dd
--- /dev/null
+++ b/src/main/java/com/example/NtfyConnection.java
@@ -0,0 +1,11 @@
+package com.example;
+
+import java.util.function.Consumer;
+
+public interface NtfyConnection {
+
+ public boolean send(String message);
+
+ public void receive(Consumer messageHandler);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/NtfyConnectionImpl.java b/src/main/java/com/example/NtfyConnectionImpl.java
new file mode 100644
index 00000000..0ce2f50a
--- /dev/null
+++ b/src/main/java/com/example/NtfyConnectionImpl.java
@@ -0,0 +1,65 @@
+package com.example;
+
+import io.github.cdimascio.dotenv.Dotenv;
+import tools.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public class NtfyConnectionImpl implements NtfyConnection {
+
+ private final HttpClient http = HttpClient.newHttpClient();
+ private final String hostName;
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ public NtfyConnectionImpl() {
+ Dotenv dotenv = Dotenv.load();
+ hostName = Objects.requireNonNull(dotenv.get("HOST_NAME"));
+ }
+
+ public NtfyConnectionImpl(String hostName) {
+ this.hostName = hostName;
+ }
+
+ @Override
+ public boolean send(String message) {
+ HttpRequest httpRequest = HttpRequest.newBuilder()
+ .POST(HttpRequest.BodyPublishers.ofString(message))
+ .header("Cache", "no")
+ .uri(URI.create(hostName + "/mytopic"))
+ .build();
+ try {
+ //Todo: handle long blocking send requests to not freeze the JavaFX thread
+ //1. Use thread send message?
+ //2. Use async?
+ var reponse = http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
+ return true;
+ } catch (IOException e) {
+ System.out.println("Error sending message");
+ } catch (InterruptedException e) {
+ System.out.println("Interruped sending message");
+ }
+ return false;
+ }
+
+ @Override
+ public void receive(Consumer messageHandler) {
+ HttpRequest httpRequest = HttpRequest.newBuilder()
+ .GET()
+ .uri(URI.create(hostName + "/mytopic/json"))
+ .build();
+
+ http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
+ .thenAccept(response -> response.body()
+ .map(s ->
+ mapper.readValue(s, NtfyMessageDto.class))
+ .filter(message -> message.event().equals("message"))
+ .peek(System.out::println)
+ .forEach(messageHandler));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/NtfyMessageDto.java b/src/main/java/com/example/NtfyMessageDto.java
new file mode 100644
index 00000000..697cccac
--- /dev/null
+++ b/src/main/java/com/example/NtfyMessageDto.java
@@ -0,0 +1,7 @@
+package com.example;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record NtfyMessageDto(String id, long time, String event, String topic, String message) {
+}
\ No newline at end of file
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 71574a27..ab9e44fc 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,6 +1,10 @@
module hellofx {
requires javafx.controls;
requires javafx.fxml;
+ requires com.fasterxml.jackson.annotation;
+ requires io.github.cdimascio.dotenv.java;
+ requires tools.jackson.databind;
+ requires java.net.http;
opens com.example to javafx.fxml;
exports com.example;
From 88f71c1b2b0b5c3504d75211caae6badacd66c8c Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Thu, 13 Nov 2025 11:41:58 +0100
Subject: [PATCH 05/17] functional app without test
---
.../java/com/example/ChatApplication.java | 23 +++++--
src/main/java/com/example/ChatController.java | 63 +++++++++++++++----
src/main/java/com/example/ChatModel.java | 27 ++++++++
src/main/resources/com/example/chat-view.fxml | 2 +-
4 files changed, 97 insertions(+), 18 deletions(-)
diff --git a/src/main/java/com/example/ChatApplication.java b/src/main/java/com/example/ChatApplication.java
index 19cc8801..9c41f7b9 100644
--- a/src/main/java/com/example/ChatApplication.java
+++ b/src/main/java/com/example/ChatApplication.java
@@ -6,14 +6,26 @@
import javafx.scene.Scene;
import javafx.stage.Stage;
+import java.io.IOException;
+
public class ChatApplication extends Application {
@Override
- public void start(Stage stage) throws Exception {
- FXMLLoader fxmlLoader = new FXMLLoader(ChatApplication.class.getResource("chat-view.fxml"));
- Parent root = fxmlLoader.load();
- Scene scene = new Scene(root, 640, 480);
- stage.setTitle("Hello MVC");
+ public void start(Stage stage) throws IOException {
+
+ NtfyConnection ntfyService = new NtfyConnectionImpl();
+ ChatModel model = new ChatModel(ntfyService);
+
+ model.startReceiving();
+
+ FXMLLoader fxmlLoader = new FXMLLoader(
+ ChatApplication.class.getResource("chat-view.fxml"));
+
+ ChatController controller = new ChatController(model);
+ fxmlLoader.setController(controller);
+
+ Scene scene = new Scene(fxmlLoader.load(), 600, 400);
+ stage.setTitle("JavaFX Ntfy Chat App");
stage.setScene(scene);
stage.show();
}
@@ -21,5 +33,4 @@ public void start(Stage stage) throws Exception {
public static void main(String[] args) {
launch();
}
-
}
\ No newline at end of file
diff --git a/src/main/java/com/example/ChatController.java b/src/main/java/com/example/ChatController.java
index 257d9048..56e3eec1 100644
--- a/src/main/java/com/example/ChatController.java
+++ b/src/main/java/com/example/ChatController.java
@@ -1,26 +1,67 @@
package com.example;
+import io.github.cdimascio.dotenv.Dotenv;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
+import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
-/**
- * Controller layer: mediates between the view (FXML) and the model.
- */
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Objects;
+
+
public class ChatController {
- private final ChatModel model = new ChatModel();
- public TextField inputTextField;
- public Button sendButton;
+ @FXML private ListView messageListView;
+ @FXML private TextField inputTextField;
+ @FXML private Button sendButton;
+
+ private final ChatModel model;
+
+ private final DateTimeFormatter timeFormatter =
+ DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.systemDefault());
- @FXML
- private Label messageLabel;
+
+ public ChatController(ChatModel model) {
+ this.model = model;
+ }
@FXML
- private void initialize() {
- if (messageLabel != null) {
- messageLabel.setText(model.getGreeting());
+ public void initialize() {
+ messageListView.setItems(model.getMessages());
+
+ messageListView.setCellFactory(lv -> new javafx.scene.control.ListCell() {
+ @Override
+ protected void updateItem(ChatMessage msg, boolean empty) {
+ super.updateItem(msg, empty);
+ if (empty || msg == null) {
+ setText(null);
+ } else {
+ String formattedTime = timeFormatter.format(Instant.ofEpochSecond(msg.timestamp()));
+ setText("[" + formattedTime + "] " + msg.content());
+ }
+ }
+ });
+
+
+ inputTextField.setOnAction(event -> sendMessageAction());
+ sendButton.setOnAction(event -> sendMessageAction());
+ }
+
+
+ private void sendMessageAction() {
+ String message = inputTextField.getText().trim();
+ if (!message.isEmpty()) {
+
+ model.sendMessage(message);
+
+
+ inputTextField.clear();
}
}
+
+
}
diff --git a/src/main/java/com/example/ChatModel.java b/src/main/java/com/example/ChatModel.java
index 0ee3d488..9b1ba30e 100644
--- a/src/main/java/com/example/ChatModel.java
+++ b/src/main/java/com/example/ChatModel.java
@@ -1,11 +1,38 @@
package com.example;
+import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class ChatModel {
private final ObservableList messages = FXCollections.observableArrayList();
+ private final NtfyConnection ntfyConnection;
+ public ChatModel(NtfyConnection ntfyConnection) {
+ this.ntfyConnection = ntfyConnection;
+ }
+ public ObservableList getMessages() {
+ return messages;
+ }
+ public void sendMessage(String text) {
+ ntfyConnection.send(text);
+ }
+ public void startReceiving() {
+
+ ntfyConnection.receive(ntfyDto -> {
+
+
+ ChatMessage chatMsg = new ChatMessage(ntfyDto.message(), ntfyDto.time());
+
+
+ Platform.runLater(() -> {
+ messages.add(chatMsg);
+ });
+ });
+ }
}
+
+
+
diff --git a/src/main/resources/com/example/chat-view.fxml b/src/main/resources/com/example/chat-view.fxml
index 95d286e9..f16975d8 100644
--- a/src/main/resources/com/example/chat-view.fxml
+++ b/src/main/resources/com/example/chat-view.fxml
@@ -8,7 +8,7 @@
-
+
From a00a88a9acc0d30c95b00919bde7a45e509a10d3 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 11:41:13 +0100
Subject: [PATCH 06/17] added test files
---
src/test/java/JavaModelTest.java | 2 ++
src/test/java/NtfyConnectionSpy.java | 2 ++
2 files changed, 4 insertions(+)
create mode 100644 src/test/java/JavaModelTest.java
create mode 100644 src/test/java/NtfyConnectionSpy.java
diff --git a/src/test/java/JavaModelTest.java b/src/test/java/JavaModelTest.java
new file mode 100644
index 00000000..e92815a5
--- /dev/null
+++ b/src/test/java/JavaModelTest.java
@@ -0,0 +1,2 @@
+public class JavaModelTest {
+}
diff --git a/src/test/java/NtfyConnectionSpy.java b/src/test/java/NtfyConnectionSpy.java
new file mode 100644
index 00000000..041d4935
--- /dev/null
+++ b/src/test/java/NtfyConnectionSpy.java
@@ -0,0 +1,2 @@
+public class NtfyConnectionSpy {
+}
From 4a31c56f808049169d0f61121707595b36210565 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 11:53:06 +0100
Subject: [PATCH 07/17] tests complete
---
pom.xml | 12 +++
src/test/java/JavaModelTest.java | 2 -
src/test/java/NtfyConnectionSpy.java | 2 -
src/test/java/com/example/JavaModelTest.java | 83 +++++++++++++++++++
.../java/com/example/NtfyConnectionSpy.java | 34 ++++++++
5 files changed, 129 insertions(+), 4 deletions(-)
delete mode 100644 src/test/java/JavaModelTest.java
delete mode 100644 src/test/java/NtfyConnectionSpy.java
create mode 100644 src/test/java/com/example/JavaModelTest.java
create mode 100644 src/test/java/com/example/NtfyConnectionSpy.java
diff --git a/pom.xml b/pom.xml
index f4540fe2..92de621c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,6 +55,18 @@
jackson-databind
3.0.1
+
+ org.wiremock
+ wiremock
+ 4.0.0-beta.15
+ test
+
+
+ org.testfx
+ testfx-core
+ 4.0.16-alpha
+ test
+
diff --git a/src/test/java/JavaModelTest.java b/src/test/java/JavaModelTest.java
deleted file mode 100644
index e92815a5..00000000
--- a/src/test/java/JavaModelTest.java
+++ /dev/null
@@ -1,2 +0,0 @@
-public class JavaModelTest {
-}
diff --git a/src/test/java/NtfyConnectionSpy.java b/src/test/java/NtfyConnectionSpy.java
deleted file mode 100644
index 041d4935..00000000
--- a/src/test/java/NtfyConnectionSpy.java
+++ /dev/null
@@ -1,2 +0,0 @@
-public class NtfyConnectionSpy {
-}
diff --git a/src/test/java/com/example/JavaModelTest.java b/src/test/java/com/example/JavaModelTest.java
new file mode 100644
index 00000000..a9735530
--- /dev/null
+++ b/src/test/java/com/example/JavaModelTest.java
@@ -0,0 +1,83 @@
+package com.example;
+
+
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
+import com.github.tomakehurst.wiremock.junit5.WireMockTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@WireMockTest
+class ChatModelTest {
+ static {
+ try {
+ javafx.application.Platform.startup(() -> {});
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+
+ @Test
+ @DisplayName("När sendMessage anropas, ska rätt meddelande skickas till anslutningen")
+ void sendMessage_callsConnectionWithCorrectMessage() {
+
+ var spy = new NtfyConnectionSpy();
+
+ var model = new ChatModel(spy);
+ String messageToSend = "Hello Ntfy World!";
+
+
+ model.sendMessage(messageToSend);
+
+
+ assertThat(spy.sentMessage).isEqualTo(messageToSend);
+ }
+
+
+ @Test
+ @DisplayName("När ett meddelande tas emot via anslutningen, ska det läggas till i Modelns lista")
+ void receiveMessage_addsToObservableList() throws Exception {
+
+ var spy = new NtfyConnectionSpy();
+ var model = new ChatModel(spy);
+
+ model.startReceiving();
+
+ long testTimestamp = System.currentTimeMillis() / 1000;
+ String receivedText = "Ett inkommande chattmeddelande";
+
+ spy.simulateMessageArrival(receivedText, testTimestamp);
+
+
+ org.testfx.util.WaitForAsyncUtils.waitForFxEvents();
+
+ assertThat(model.getMessages()).hasSize(1);
+ ChatMessage received = model.getMessages().get(0);
+
+ assertEquals(receivedText, received.content());
+ assertEquals(testTimestamp, received.timestamp());
+ }
+
+ @Test
+ @DisplayName("sendMessage ska skicka en POST-förfrågan till WireMock-servern med rätt body")
+ void sendMessageToFakeServer_viaNtfyConnectionImpl(WireMockRuntimeInfo wmRuntimeInfo) {
+ var con = new NtfyConnectionImpl("http://localhost:" + wmRuntimeInfo.getHttpPort());
+ var model = new ChatModel(con);
+ String messageToSend = "Test Body Content";
+
+
+ stubFor(post("/mytopic").willReturn(ok()));
+
+
+ model.sendMessage(messageToSend);
+
+
+ verify(postRequestedFor(urlEqualTo("/mytopic"))
+ .withRequestBody(matching(messageToSend)));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/example/NtfyConnectionSpy.java b/src/test/java/com/example/NtfyConnectionSpy.java
new file mode 100644
index 00000000..05bf789d
--- /dev/null
+++ b/src/test/java/com/example/NtfyConnectionSpy.java
@@ -0,0 +1,34 @@
+package com.example;
+
+import java.util.function.Consumer;
+
+public class NtfyConnectionSpy implements NtfyConnection {
+
+
+ public String sentMessage = null;
+
+
+ public Consumer receivedHandler = null;
+
+ @Override
+ public boolean send(String message) {
+ this.sentMessage = message; // Fångar meddelandet
+ return true;
+ }
+
+ @Override
+ public void receive(Consumer messageHandler) {
+ this.receivedHandler = messageHandler; // Fångar hanteringsfunktionen
+ }
+
+
+ public void simulateMessageArrival(String message, long timestamp) {
+ if (receivedHandler != null) {
+ // Skapa ett fejkat DTO och skicka det till den fångade hanteraren (Consumer)
+ NtfyMessageDto fakeDto = new NtfyMessageDto(
+ "fake-id", timestamp, "message", "mytopic", message
+ );
+ receivedHandler.accept(fakeDto);
+ }
+ }
+}
\ No newline at end of file
From 40c96db79ede1586c035bad218353eb05e3d15c8 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 12:04:12 +0100
Subject: [PATCH 08/17] changed cashe and updated theme
---
.../java/com/example/NtfyConnectionImpl.java | 2 +-
src/main/resources/com/example/chat-style.css | 46 +++++++++++++++++++
src/main/resources/com/example/chat-view.fxml | 4 ++
3 files changed, 51 insertions(+), 1 deletion(-)
create mode 100644 src/main/resources/com/example/chat-style.css
diff --git a/src/main/java/com/example/NtfyConnectionImpl.java b/src/main/java/com/example/NtfyConnectionImpl.java
index 0ce2f50a..24002530 100644
--- a/src/main/java/com/example/NtfyConnectionImpl.java
+++ b/src/main/java/com/example/NtfyConnectionImpl.java
@@ -30,7 +30,7 @@ public NtfyConnectionImpl(String hostName) {
public boolean send(String message) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
- .header("Cache", "no")
+ //.header("Cache", "no")
.uri(URI.create(hostName + "/mytopic"))
.build();
try {
diff --git a/src/main/resources/com/example/chat-style.css b/src/main/resources/com/example/chat-style.css
new file mode 100644
index 00000000..040d8d64
--- /dev/null
+++ b/src/main/resources/com/example/chat-style.css
@@ -0,0 +1,46 @@
+
+.root {
+ -bg-light: #e5e5e5;
+ -text-color: #363636;
+ -primary: #4c76c2;
+ -secondary: #a8c64b;
+ -neutral: #747474;
+ -accent: #5e6ec7;
+ -radius-box: 20px;
+}
+
+.root {
+ -fx-background-color: -bg-light;
+ -fx-font-family: "Arial";
+}
+
+#messageListView {
+ -fx-background-color: -neutral;
+ -fx-border-color: -primary;
+ -fx-border-width: 2px;
+ -fx-border-radius: -radius-box;
+ -fx-padding: 10px;
+ -fx-background-radius: -radius-box;
+}
+
+#inputTextField {
+ -fx-background-color: white;
+ -fx-text-fill: -text-color;
+ -fx-border-color: -primary;
+ -fx-border-width: 1px;
+ -fx-border-radius: -radius-box;
+ -fx-background-radius: -radius-box;
+ -fx-padding: 8px 15px;
+}
+
+.button {
+ -fx-font-size: 14px;
+ -fx-background-radius: -radius-box;
+ -fx-background-color: -primary;
+ -fx-text-fill: white;
+ -fx-padding: 8px 15px;
+}
+
+.button:hover {
+ -fx-background-color: derive(-primary, 20%);
+}
\ No newline at end of file
diff --git a/src/main/resources/com/example/chat-view.fxml b/src/main/resources/com/example/chat-view.fxml
index f16975d8..8b27d939 100644
--- a/src/main/resources/com/example/chat-view.fxml
+++ b/src/main/resources/com/example/chat-view.fxml
@@ -8,7 +8,11 @@
+
+
+
+
From 3aeaad122b14f97455736d13af4f9b896870dcb3 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 12:19:11 +0100
Subject: [PATCH 09/17] update to test
---
.../com/example/{JavaModelTest.java => ChatModelTest.java} | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
rename src/test/java/com/example/{JavaModelTest.java => ChatModelTest.java} (96%)
diff --git a/src/test/java/com/example/JavaModelTest.java b/src/test/java/com/example/ChatModelTest.java
similarity index 96%
rename from src/test/java/com/example/JavaModelTest.java
rename to src/test/java/com/example/ChatModelTest.java
index a9735530..57c18832 100644
--- a/src/test/java/com/example/JavaModelTest.java
+++ b/src/test/java/com/example/ChatModelTest.java
@@ -15,7 +15,8 @@
class ChatModelTest {
static {
try {
- javafx.application.Platform.startup(() -> {});
+ javafx.application.Platform.startup(() -> {
+ });
} catch (IllegalStateException e) {
}
@@ -80,4 +81,4 @@ void sendMessageToFakeServer_viaNtfyConnectionImpl(WireMockRuntimeInfo wmRuntime
verify(postRequestedFor(urlEqualTo("/mytopic"))
.withRequestBody(matching(messageToSend)));
}
-}
\ No newline at end of file
+}
From d3464f7a900e4d123a81e5b7e44864df2d6279e6 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 12:38:41 +0100
Subject: [PATCH 10/17] rewrite after feedback
---
.../java/com/example/NtfyConnectionImpl.java | 45 ++++++++++++-------
src/main/java/module-info.java | 2 +-
src/test/java/com/example/ChatModelTest.java | 10 ++---
3 files changed, 34 insertions(+), 23 deletions(-)
diff --git a/src/main/java/com/example/NtfyConnectionImpl.java b/src/main/java/com/example/NtfyConnectionImpl.java
index 24002530..2b11a2be 100644
--- a/src/main/java/com/example/NtfyConnectionImpl.java
+++ b/src/main/java/com/example/NtfyConnectionImpl.java
@@ -18,8 +18,21 @@ public class NtfyConnectionImpl implements NtfyConnection {
private final ObjectMapper mapper = new ObjectMapper();
public NtfyConnectionImpl() {
- Dotenv dotenv = Dotenv.load();
- hostName = Objects.requireNonNull(dotenv.get("HOST_NAME"));
+ String loadedHostName = null;
+ try {
+ Dotenv dotenv = Dotenv.load();
+ loadedHostName = dotenv.get("HOST_NAME");
+ } catch (Exception e) {
+ System.err.println("WARNING: Could not load .env file for HOST_NAME. Using fallback.");
+ }
+
+ this.hostName = (loadedHostName != null)
+ ? loadedHostName
+ : "http://localhost:8080";
+
+ if (this.hostName.equals("http://localhost:8080")) {
+ System.out.println("DEBUG: NtfyConnectionImpl running in test/fallback mode.");
+ }
}
public NtfyConnectionImpl(String hostName) {
@@ -30,21 +43,23 @@ public NtfyConnectionImpl(String hostName) {
public boolean send(String message) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
- //.header("Cache", "no")
.uri(URI.create(hostName + "/mytopic"))
.build();
- try {
- //Todo: handle long blocking send requests to not freeze the JavaFX thread
- //1. Use thread send message?
- //2. Use async?
- var reponse = http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
- return true;
- } catch (IOException e) {
- System.out.println("Error sending message");
- } catch (InterruptedException e) {
- System.out.println("Interruped sending message");
- }
- return false;
+
+ http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding())
+ .thenAccept(response -> {
+ if (response.statusCode() >= 200 && response.statusCode() < 300) {
+ System.out.println("Message sent successfully.");
+ } else {
+ System.err.println("Error while sending: " + response.statusCode());
+ }
+ })
+ .exceptionally(e -> {
+ System.err.println("Network issue: " + e.getMessage());
+ return null;
+ });
+
+ return true;
}
@Override
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index ab9e44fc..85e1995c 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,4 +1,4 @@
-module hellofx {
+module chatapp {
requires javafx.controls;
requires javafx.fxml;
requires com.fasterxml.jackson.annotation;
diff --git a/src/test/java/com/example/ChatModelTest.java b/src/test/java/com/example/ChatModelTest.java
index 57c18832..6fa2a8db 100644
--- a/src/test/java/com/example/ChatModelTest.java
+++ b/src/test/java/com/example/ChatModelTest.java
@@ -3,7 +3,6 @@
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
-import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
@@ -24,7 +23,6 @@ class ChatModelTest {
@Test
- @DisplayName("När sendMessage anropas, ska rätt meddelande skickas till anslutningen")
void sendMessage_callsConnectionWithCorrectMessage() {
var spy = new NtfyConnectionSpy();
@@ -41,7 +39,7 @@ void sendMessage_callsConnectionWithCorrectMessage() {
@Test
- @DisplayName("När ett meddelande tas emot via anslutningen, ska det läggas till i Modelns lista")
+
void receiveMessage_addsToObservableList() throws Exception {
var spy = new NtfyConnectionSpy();
@@ -65,18 +63,16 @@ void receiveMessage_addsToObservableList() throws Exception {
}
@Test
- @DisplayName("sendMessage ska skicka en POST-förfrågan till WireMock-servern med rätt body")
- void sendMessageToFakeServer_viaNtfyConnectionImpl(WireMockRuntimeInfo wmRuntimeInfo) {
+ void sendMessageToFakeServer_viaNtfyConnectionImpl(WireMockRuntimeInfo wmRuntimeInfo) throws InterruptedException {
var con = new NtfyConnectionImpl("http://localhost:" + wmRuntimeInfo.getHttpPort());
var model = new ChatModel(con);
String messageToSend = "Test Body Content";
-
stubFor(post("/mytopic").willReturn(ok()));
-
model.sendMessage(messageToSend);
+ Thread.sleep(500);
verify(postRequestedFor(urlEqualTo("/mytopic"))
.withRequestBody(matching(messageToSend)));
From fc0520703ed132e193ca12b1cc282308adfcecb3 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 12:43:02 +0100
Subject: [PATCH 11/17] added dependency to get the test to work
---
pom.xml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/pom.xml b/pom.xml
index 92de621c..1edf57e0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,6 +67,12 @@
4.0.16-alpha
test
+
+ org.slf4j
+ slf4j-simple
+ 2.0.12
+ test
+
From 5a6b5097e0e0f850ce64de088e022f6d43259cfc Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 13:11:57 +0100
Subject: [PATCH 12/17] hopefully fixed test
---
pom.xml | 6 ++
src/main/java/com/example/ChatModel.java | 11 +--
src/main/java/com/example/NtfyConnection.java | 8 +-
.../java/com/example/NtfyConnectionImpl.java | 21 +++--
src/test/java/com/example/ChatModelTest.java | 84 ++++++++++---------
.../java/com/example/NtfyConnectionSpy.java | 26 +++---
6 files changed, 79 insertions(+), 77 deletions(-)
diff --git a/pom.xml b/pom.xml
index 1edf57e0..6687bc34 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,6 +73,12 @@
2.0.12
test
+
+ org.awaitility
+ awaitility
+ 4.3.0
+ test
+
diff --git a/src/main/java/com/example/ChatModel.java b/src/main/java/com/example/ChatModel.java
index 9b1ba30e..2a096683 100644
--- a/src/main/java/com/example/ChatModel.java
+++ b/src/main/java/com/example/ChatModel.java
@@ -16,23 +16,18 @@ public ChatModel(NtfyConnection ntfyConnection) {
public ObservableList getMessages() {
return messages;
}
+
public void sendMessage(String text) {
ntfyConnection.send(text);
}
- public void startReceiving() {
+ public void startReceiving() {
ntfyConnection.receive(ntfyDto -> {
-
-
ChatMessage chatMsg = new ChatMessage(ntfyDto.message(), ntfyDto.time());
-
Platform.runLater(() -> {
messages.add(chatMsg);
});
});
}
-}
-
-
-
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/NtfyConnection.java b/src/main/java/com/example/NtfyConnection.java
index c83095dd..d26fecaa 100644
--- a/src/main/java/com/example/NtfyConnection.java
+++ b/src/main/java/com/example/NtfyConnection.java
@@ -1,11 +1,9 @@
package com.example;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public interface NtfyConnection {
-
- public boolean send(String message);
-
- public void receive(Consumer messageHandler);
-
+ CompletableFuture send(String message);
+ void receive(Consumer messageHandler);
}
\ No newline at end of file
diff --git a/src/main/java/com/example/NtfyConnectionImpl.java b/src/main/java/com/example/NtfyConnectionImpl.java
index 2b11a2be..3dd8dd72 100644
--- a/src/main/java/com/example/NtfyConnectionImpl.java
+++ b/src/main/java/com/example/NtfyConnectionImpl.java
@@ -3,12 +3,11 @@
import io.github.cdimascio.dotenv.Dotenv;
import tools.jackson.databind.ObjectMapper;
-import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
-import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class NtfyConnectionImpl implements NtfyConnection {
@@ -40,13 +39,13 @@ public NtfyConnectionImpl(String hostName) {
}
@Override
- public boolean send(String message) {
+ public CompletableFuture send(String message) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
.uri(URI.create(hostName + "/mytopic"))
.build();
- http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding())
+ return http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding())
.thenAccept(response -> {
if (response.statusCode() >= 200 && response.statusCode() < 300) {
System.out.println("Message sent successfully.");
@@ -58,8 +57,6 @@ public boolean send(String message) {
System.err.println("Network issue: " + e.getMessage());
return null;
});
-
- return true;
}
@Override
@@ -71,9 +68,15 @@ public void receive(Consumer messageHandler) {
http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> response.body()
- .map(s ->
- mapper.readValue(s, NtfyMessageDto.class))
- .filter(message -> message.event().equals("message"))
+ .map(s -> {
+ try {
+ return mapper.readValue(s, NtfyMessageDto.class);
+ } catch (Exception e) {
+ System.err.println("Failed to parse message: " + e.getMessage());
+ return null;
+ }
+ })
+ .filter(messageDto -> messageDto != null && messageDto.event().equals("message"))
.peek(System.out::println)
.forEach(messageHandler));
}
diff --git a/src/test/java/com/example/ChatModelTest.java b/src/test/java/com/example/ChatModelTest.java
index 6fa2a8db..9887dd87 100644
--- a/src/test/java/com/example/ChatModelTest.java
+++ b/src/test/java/com/example/ChatModelTest.java
@@ -1,80 +1,86 @@
package com.example;
+import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.awaitility.Awaitility;
+import javafx.application.Platform;
+
+import java.time.Duration;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
@WireMockTest
class ChatModelTest {
- static {
+
+ @BeforeAll
+ static void initJavaFX() {
try {
- javafx.application.Platform.startup(() -> {
- });
+ Platform.startup(() -> {});
+ Platform.setImplicitExit(false);
} catch (IllegalStateException e) {
-
}
}
@Test
- void sendMessage_callsConnectionWithCorrectMessage() {
-
+ void sendMessageCallsConnectionWithMessagesToSend() {
var spy = new NtfyConnectionSpy();
-
var model = new ChatModel(spy);
- String messageToSend = "Hello Ntfy World!";
-
+ String messageToSend = "Test Message 123";
model.sendMessage(messageToSend);
-
- assertThat(spy.sentMessage).isEqualTo(messageToSend);
+ assertThat(spy.message).isEqualTo(messageToSend);
}
-
@Test
+ void sendMessageToFakeServer_viaNtfyConnectionImpl(WireMockRuntimeInfo wmRunTimeInfo) throws InterruptedException {
+ var con = new NtfyConnectionImpl("http://localhost:" + wmRunTimeInfo.getHttpPort());
+ var model = new ChatModel(con);
+ String messageToSend = "Test Message 123";
- void receiveMessage_addsToObservableList() throws Exception {
-
- var spy = new NtfyConnectionSpy();
- var model = new ChatModel(spy);
-
- model.startReceiving();
+ stubFor(post("/mytopic").willReturn(ok()));
- long testTimestamp = System.currentTimeMillis() / 1000;
- String receivedText = "Ett inkommande chattmeddelande";
+ model.sendMessage(messageToSend);
- spy.simulateMessageArrival(receivedText, testTimestamp);
+ Thread.sleep(500);
+ WireMock.verify(postRequestedFor(urlEqualTo("/mytopic"))
+ .withRequestBody(matching(messageToSend)));
+ }
- org.testfx.util.WaitForAsyncUtils.waitForFxEvents();
- assertThat(model.getMessages()).hasSize(1);
- ChatMessage received = model.getMessages().get(0);
+ @Test
+ void checkReceivedMessagesAfterSendingAMessageToAFakeServer(WireMockRuntimeInfo wmRunTimeInfo) {
+ var conImp = new NtfyConnectionImpl("http://localhost:" + wmRunTimeInfo.getHttpPort());
- assertEquals(receivedText, received.content());
- assertEquals(testTimestamp, received.timestamp());
- }
+ String expectedMessage = "Async Data Received";
+ long expectedTimestamp = System.currentTimeMillis();
- @Test
- void sendMessageToFakeServer_viaNtfyConnectionImpl(WireMockRuntimeInfo wmRuntimeInfo) throws InterruptedException {
- var con = new NtfyConnectionImpl("http://localhost:" + wmRuntimeInfo.getHttpPort());
- var model = new ChatModel(con);
- String messageToSend = "Test Body Content";
+ stubFor(get("/mytopic/json")
+ .willReturn(aResponse()
+ .withHeader("Content-type", "application/json")
+ .withBody("{\"event\": \"message\",\"message\": \"" + expectedMessage + "\", \"time\": \"" + expectedTimestamp + "\"}")));
- stubFor(post("/mytopic").willReturn(ok()));
+ var model = new ChatModel(conImp);
+ model.startReceiving();
- model.sendMessage(messageToSend);
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(4))
+ .pollInterval(Duration.ofMillis(100))
+ .untilAsserted(() -> {
+ assertThat(model.getMessages()).isNotEmpty();
- Thread.sleep(500);
+ assertThat(model.getMessages().getLast().content()).isEqualTo(expectedMessage);
- verify(postRequestedFor(urlEqualTo("/mytopic"))
- .withRequestBody(matching(messageToSend)));
+ assertThat(model.getMessages().getLast().timestamp()).isEqualTo(expectedTimestamp);
+ });
}
-}
+}
\ No newline at end of file
diff --git a/src/test/java/com/example/NtfyConnectionSpy.java b/src/test/java/com/example/NtfyConnectionSpy.java
index 05bf789d..8d11828b 100644
--- a/src/test/java/com/example/NtfyConnectionSpy.java
+++ b/src/test/java/com/example/NtfyConnectionSpy.java
@@ -1,34 +1,28 @@
package com.example;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class NtfyConnectionSpy implements NtfyConnection {
+ String message;
+ private Consumer messageHandler;
- public String sentMessage = null;
-
-
- public Consumer receivedHandler = null;
@Override
- public boolean send(String message) {
- this.sentMessage = message; // Fångar meddelandet
- return true;
+ public CompletableFuture send(String message) {
+ this.message = message;
+ return CompletableFuture.completedFuture(null);
}
@Override
public void receive(Consumer messageHandler) {
- this.receivedHandler = messageHandler; // Fångar hanteringsfunktionen
+ this.messageHandler = messageHandler;
}
- public void simulateMessageArrival(String message, long timestamp) {
- if (receivedHandler != null) {
- // Skapa ett fejkat DTO och skicka det till den fångade hanteraren (Consumer)
- NtfyMessageDto fakeDto = new NtfyMessageDto(
- "fake-id", timestamp, "message", "mytopic", message
- );
- receivedHandler.accept(fakeDto);
- }
+ public void simulateIncomingMessage(NtfyMessageDto messageDto){
+ if (messageHandler != null)
+ messageHandler.accept(messageDto);
}
}
\ No newline at end of file
From 72f9ef35389f333079d598fab9a1815e217c783f Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 13:14:36 +0100
Subject: [PATCH 13/17] hopefully fixed test again
---
pom.xml | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/pom.xml b/pom.xml
index 6687bc34..f8a02a87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,6 +82,19 @@
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+
+ -Djava.awt.headless=true
+ -Dprism.order=sw
+ -Dtestfx.robot=glass
+ -Dtestfx.headless=true
+
+
+
org.openjfx
javafx-maven-plugin
From dbcb3bf5734f049c548d6312f7e977ced79c22e2 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 13:17:13 +0100
Subject: [PATCH 14/17] pom update
---
pom.xml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index f8a02a87..75005951 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,6 +15,7 @@
3.27.6
5.20.0
25
+ 3.2.5
@@ -85,9 +86,11 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.2.5
+ ${surefire.version}
+ --add-exports javafx.graphics/com.sun.glass.ui=ALL-UNNAMED
+ --add-exports javafx.base/com.sun.javafx.runtime=ALL-UNNAMED
-Djava.awt.headless=true
-Dprism.order=sw
-Dtestfx.robot=glass
From dc6cbd9d3cda81b0ce89746b6792783571eeac89 Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 13:25:03 +0100
Subject: [PATCH 15/17] simpler tests
---
pom.xml | 67 ++++++++------------
src/test/java/com/example/ChatModelTest.java | 27 ++------
2 files changed, 33 insertions(+), 61 deletions(-)
diff --git a/pom.xml b/pom.xml
index 75005951..1ce2ce2b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,27 +15,8 @@
3.27.6
5.20.0
25
- 3.2.5
-
- org.junit.jupiter
- junit-jupiter
- ${junit.jupiter.version}
- test
-
-
- org.assertj
- assertj-core
- ${assertj.core.version}
- test
-
-
- org.mockito
- mockito-junit-jupiter
- ${mockito.version}
- test
-
org.openjfx
javafx-controls
@@ -56,15 +37,40 @@
jackson-databind
3.0.1
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.jupiter.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.core.version}
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockito.version}
+ test
+
org.wiremock
wiremock
4.0.0-beta.15
test
+
+ org.awaitility
+ awaitility
+ 4.3.0
+ test
+
org.testfx
- testfx-core
+ testfx-junit5
4.0.16-alpha
test
@@ -74,30 +80,9 @@
2.0.12
test
-
- org.awaitility
- awaitility
- 4.3.0
- test
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- ${surefire.version}
-
-
- --add-exports javafx.graphics/com.sun.glass.ui=ALL-UNNAMED
- --add-exports javafx.base/com.sun.javafx.runtime=ALL-UNNAMED
- -Djava.awt.headless=true
- -Dprism.order=sw
- -Dtestfx.robot=glass
- -Dtestfx.headless=true
-
-
-
org.openjfx
javafx-maven-plugin
diff --git a/src/test/java/com/example/ChatModelTest.java b/src/test/java/com/example/ChatModelTest.java
index 9887dd87..ec0112cc 100644
--- a/src/test/java/com/example/ChatModelTest.java
+++ b/src/test/java/com/example/ChatModelTest.java
@@ -1,13 +1,11 @@
package com.example;
-
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.awaitility.Awaitility;
+import org.testfx.framework.junit5.ApplicationTest;
import javafx.application.Platform;
import java.time.Duration;
@@ -15,19 +13,8 @@
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.assertj.core.api.Assertions.assertThat;
-
@WireMockTest
-class ChatModelTest {
-
- @BeforeAll
- static void initJavaFX() {
- try {
- Platform.startup(() -> {});
- Platform.setImplicitExit(false);
- } catch (IllegalStateException e) {
- }
- }
-
+class ChatModelTest extends ApplicationTest {
@Test
void sendMessageCallsConnectionWithMessagesToSend() {
@@ -76,11 +63,11 @@ void checkReceivedMessagesAfterSendingAMessageToAFakeServer(WireMockRuntimeInfo
.atMost(Duration.ofSeconds(4))
.pollInterval(Duration.ofMillis(100))
.untilAsserted(() -> {
- assertThat(model.getMessages()).isNotEmpty();
-
- assertThat(model.getMessages().getLast().content()).isEqualTo(expectedMessage);
-
- assertThat(model.getMessages().getLast().timestamp()).isEqualTo(expectedTimestamp);
+ Platform.runLater(() -> {
+ assertThat(model.getMessages()).isNotEmpty();
+ assertThat(model.getMessages().getLast().content()).isEqualTo(expectedMessage);
+ assertThat(model.getMessages().getLast().timestamp()).isEqualTo(expectedTimestamp);
+ });
});
}
}
\ No newline at end of file
From 03df1f37dd67860951b2d07a0d8a8998f5ab269c Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 13:33:41 +0100
Subject: [PATCH 16/17] testing
---
src/test/java/com/example/ChatModelTest.java | 34 ++++++++++++--------
1 file changed, 21 insertions(+), 13 deletions(-)
diff --git a/src/test/java/com/example/ChatModelTest.java b/src/test/java/com/example/ChatModelTest.java
index ec0112cc..49d6e675 100644
--- a/src/test/java/com/example/ChatModelTest.java
+++ b/src/test/java/com/example/ChatModelTest.java
@@ -4,14 +4,15 @@
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import org.junit.jupiter.api.Test;
-import org.awaitility.Awaitility;
import org.testfx.framework.junit5.ApplicationTest;
import javafx.application.Platform;
-import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
@WireMockTest
class ChatModelTest extends ApplicationTest {
@@ -45,7 +46,7 @@ void sendMessageToFakeServer_viaNtfyConnectionImpl(WireMockRuntimeInfo wmRunTime
@Test
- void checkReceivedMessagesAfterSendingAMessageToAFakeServer(WireMockRuntimeInfo wmRunTimeInfo) {
+ void checkReceivedMessagesAfterSendingAMessageToAFakeServer(WireMockRuntimeInfo wmRunTimeInfo) throws InterruptedException {
var conImp = new NtfyConnectionImpl("http://localhost:" + wmRunTimeInfo.getHttpPort());
String expectedMessage = "Async Data Received";
@@ -59,15 +60,22 @@ void checkReceivedMessagesAfterSendingAMessageToAFakeServer(WireMockRuntimeInfo
var model = new ChatModel(conImp);
model.startReceiving();
- Awaitility.await()
- .atMost(Duration.ofSeconds(4))
- .pollInterval(Duration.ofMillis(100))
- .untilAsserted(() -> {
- Platform.runLater(() -> {
- assertThat(model.getMessages()).isNotEmpty();
- assertThat(model.getMessages().getLast().content()).isEqualTo(expectedMessage);
- assertThat(model.getMessages().getLast().timestamp()).isEqualTo(expectedTimestamp);
- });
- });
+ CountDownLatch latch = new CountDownLatch(1);
+
+ Platform.runLater(() -> {
+ try {
+ assertThat(model.getMessages()).isNotEmpty();
+ assertThat(model.getMessages().getLast().content()).isEqualTo(expectedMessage);
+ assertThat(model.getMessages().getLast().timestamp()).isEqualTo(expectedTimestamp);
+ } catch (AssertionError e) {
+ fail("Assertion failed inside Platform.runLater: " + e.getMessage());
+ } finally {
+ latch.countDown();
+ }
+ });
+
+ assertThat(latch.await(5, TimeUnit.SECONDS))
+ .as("Timed out waiting for JavaFX thread to execute assertions.")
+ .isTrue();
}
}
\ No newline at end of file
From d081f822f900cf014a8bda3b08ba69e99c33bfff Mon Sep 17 00:00:00 2001
From: Johan Karlsson <93186588+gurkvatten@users.noreply.github.com>
Date: Fri, 14 Nov 2025 14:53:52 +0100
Subject: [PATCH 17/17] test fixes
---
src/main/java/com/example/ChatModel.java | 4 +--
src/test/java/com/example/ChatModelTest.java | 38 ++++++++------------
2 files changed, 15 insertions(+), 27 deletions(-)
diff --git a/src/main/java/com/example/ChatModel.java b/src/main/java/com/example/ChatModel.java
index 2a096683..c69dae26 100644
--- a/src/main/java/com/example/ChatModel.java
+++ b/src/main/java/com/example/ChatModel.java
@@ -25,9 +25,7 @@ public void startReceiving() {
ntfyConnection.receive(ntfyDto -> {
ChatMessage chatMsg = new ChatMessage(ntfyDto.message(), ntfyDto.time());
- Platform.runLater(() -> {
- messages.add(chatMsg);
- });
+ messages.add(chatMsg);
});
}
}
\ No newline at end of file
diff --git a/src/test/java/com/example/ChatModelTest.java b/src/test/java/com/example/ChatModelTest.java
index 49d6e675..d2c9920d 100644
--- a/src/test/java/com/example/ChatModelTest.java
+++ b/src/test/java/com/example/ChatModelTest.java
@@ -4,18 +4,14 @@
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import org.junit.jupiter.api.Test;
-import org.testfx.framework.junit5.ApplicationTest;
-import javafx.application.Platform;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import org.awaitility.Awaitility;
+import java.time.Duration;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.fail;
@WireMockTest
-class ChatModelTest extends ApplicationTest {
+class ChatModelTest {
@Test
void sendMessageCallsConnectionWithMessagesToSend() {
@@ -50,6 +46,8 @@ void checkReceivedMessagesAfterSendingAMessageToAFakeServer(WireMockRuntimeInfo
var conImp = new NtfyConnectionImpl("http://localhost:" + wmRunTimeInfo.getHttpPort());
String expectedMessage = "Async Data Received";
+ // Använd System.currentTimeMillis() om din ChatModel skickar detta;
+ // annars, använd ett statiskt värde för att vara säker.
long expectedTimestamp = System.currentTimeMillis();
stubFor(get("/mytopic/json")
@@ -60,22 +58,14 @@ void checkReceivedMessagesAfterSendingAMessageToAFakeServer(WireMockRuntimeInfo
var model = new ChatModel(conImp);
model.startReceiving();
- CountDownLatch latch = new CountDownLatch(1);
-
- Platform.runLater(() -> {
- try {
- assertThat(model.getMessages()).isNotEmpty();
- assertThat(model.getMessages().getLast().content()).isEqualTo(expectedMessage);
- assertThat(model.getMessages().getLast().timestamp()).isEqualTo(expectedTimestamp);
- } catch (AssertionError e) {
- fail("Assertion failed inside Platform.runLater: " + e.getMessage());
- } finally {
- latch.countDown();
- }
- });
-
- assertThat(latch.await(5, TimeUnit.SECONDS))
- .as("Timed out waiting for JavaFX thread to execute assertions.")
- .isTrue();
+ // Awaitility: Väntar aktivt tills listan fylls.
+ Awaitility.await()
+ .atMost(Duration.ofSeconds(10))
+ .pollInterval(Duration.ofMillis(100))
+ .until(() -> model.getMessages().size() > 0);
+
+ assertThat(model.getMessages()).isNotEmpty();
+ assertThat(model.getMessages().getLast().content()).isEqualTo(expectedMessage);
+ assertThat(model.getMessages().getLast().timestamp()).isEqualTo(expectedTimestamp);
}
}
\ No newline at end of file