diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1199c67..7311676 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,15 +3,33 @@
# and
# https://gitlab.stud.idi.ntnu.no/tdt4140-staff/examples/-/blob/master/.gitlab-ci.yml
-image: maven:3.6.3-openjdk-15
+image: maven:3-openjdk-15-slim
variables:
+
# This will suppress any download for dependencies and plugins or upload messages which would clutter the console log.
# `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
- MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
+ MAVEN_OPTS: " \
+ -Dhttps.protocols=TLSv1.2 \
+ -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository \
+ -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN \
+ -Dorg.slf4j.simpleLogger.showDateTime=true \
+ -Djava.awt.headless=true"
+
# As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used
# when running from the command line.
- MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
+ MAVEN_CLI_OPTS: " \
+ --batch-mode \
+ --errors \
+ --fail-at-end \
+ --show-version \
+ -Dprism.verbose=true \
+ -Dtestfx.robot=glass \
+ -Dtestfx.headless=true \
+ -Dglass.platform=Monocle \
+ -Dprism.order=sw \
+ -Dprism.text=t2k \
+ -Dtestfx.setup.timeout=60000"
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
@@ -38,6 +56,8 @@ unittest:
stage: test
needs: [build]
script:
+ - "apt update"
+ - "apt install -y openjfx"
- "mvn package $MAVEN_CLI_OPTS"
artifacts:
paths:
@@ -46,12 +66,15 @@ unittest:
reports:
junit:
- target/surefire-reports/TEST-*.xml
- - target/failsafe-reports/TEST-*.xml
+ # TODO: Separate unit tests and integration tests
+ # - target/failsafe-reports/TEST-*.xml
generate-coverage:
stage: docs
script:
- - 'mvn clean jacoco:prepare-agent test jacoco:report'
+ - "apt update"
+ - "apt install -y openjfx"
+ - 'mvn clean jacoco:prepare-agent test $MAVEN_CLI_OPTS jacoco:report'
- 'cat target/site/jacoco/index.html'
coverage: '/Total.*?([0-9]{1,3})%/'
artifacts:
diff --git a/pom.xml b/pom.xml
index 9f82b77..0250a57 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,7 +42,6 @@
3.2.0-01
-
org.junit.jupiter
@@ -65,6 +64,36 @@
test
+
+
+ org.hamcrest
+ hamcrest
+ 2.1
+ test
+
+
+
+
+ org.testfx
+ openjfx-monocle
+ jdk-12.0.1+2
+ test
+
+
+
+
+ org.mockito
+ mockito-inline
+ 3.8.0
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 2.23.0
+ test
+
+
org.apache.maven.plugins
@@ -133,7 +162,6 @@
-
diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java
index 7c161aa..ec43f2d 100644
--- a/src/main/java/app/Main.java
+++ b/src/main/java/app/Main.java
@@ -35,7 +35,8 @@ public class Main extends Application {
*/
private void setupWindow(Stage window) {
window.setTitle(TITLE);
- window.getIcons().add(new Image(getClass().getResourceAsStream(ICON_PATH)));
+ if (window.getIcons().isEmpty())
+ window.getIcons().add(new Image(getClass().getResourceAsStream(ICON_PATH)));
}
/**
@@ -53,6 +54,7 @@ public class Main extends Application {
*/
private void createScene() {
this.scene = new Scene(fxmlRoot);
+ this.scene.setUserData(this.fxmlLoader);
Model.setScene(scene);
}
diff --git a/src/main/java/app/MainController.java b/src/main/java/app/MainController.java
index 428f83e..ed6b470 100644
--- a/src/main/java/app/MainController.java
+++ b/src/main/java/app/MainController.java
@@ -66,6 +66,16 @@ public class MainController implements Initializable {
return hostServices;
}
+ //TODO: Document
+ public List getInnerControllers() {
+ return List.of(
+ editorController,
+ filetreeController,
+ modelineController,
+ menubarController
+ );
+ }
+
/**
* Set a reference to the global Host Services API
*
diff --git a/src/main/java/app/controllers/EditorController.java b/src/main/java/app/controllers/EditorController.java
index 61981b4..28e1b4c 100644
--- a/src/main/java/app/controllers/EditorController.java
+++ b/src/main/java/app/controllers/EditorController.java
@@ -66,6 +66,11 @@ public class EditorController implements Initializable, Controller, FileManageme
this.eventBus.register(this);
}
+ // TODO: document
+ public CodeArea getEditor() {
+ return editor;
+ }
+
/**
* Applies highlighting to the editor.
*
@@ -146,21 +151,20 @@ public class EditorController implements Initializable, Controller, FileManageme
* @throws FileNotFoundException
*/
public void setEditorContent(String filePath) {
- if (filePath == null) {
- editor.clear();
- editor.appendText("// New File");
- return;
- }
+ // if (filePath == null) {
+ // editor.clear();
+ // editor.appendText("// New File");
+ // return;
+ // }
try (Scanner sc = new Scanner(new File(filePath))) {
- if (filePath.endsWith(".java") || filePath.endsWith(".md")) {
+ // if (filePath.endsWith(".java") || filePath.endsWith(".md")) {
editor.clear();
while (sc.hasNextLine()) {
- editor.appendText(sc.nextLine());
- editor.appendText("\n");
+ editor.appendText(sc.nextLine() + "\n");
}
- } else {
- throw new FileNotFoundException();
- }
+ // } else {
+ // throw new FileNotFoundException();
+ // }
} catch (FileNotFoundException ex) {
Alert error = new Alert(AlertType.ERROR);
@@ -208,8 +212,8 @@ public class EditorController implements Initializable, Controller, FileManageme
* Updates Code Area (read from file) whenever the FileSelected is changed
*/
@Subscribe
- private void handle(FileSelectedEvent event) {
- this.setEditorContent(event.getPath());
+ public void handle(FileSelectedEvent event) {
+ this.setEditorContent(event.getPath());
}
/**
@@ -226,7 +230,7 @@ public class EditorController implements Initializable, Controller, FileManageme
}
@Subscribe
- private void handle(ToggleCommentEvent event) {
+ public void handle(ToggleCommentEvent event) {
this.toggleComment();
}
diff --git a/src/main/java/app/controllers/FiletreeController.java b/src/main/java/app/controllers/FiletreeController.java
index 0452544..68184d3 100644
--- a/src/main/java/app/controllers/FiletreeController.java
+++ b/src/main/java/app/controllers/FiletreeController.java
@@ -9,6 +9,7 @@ import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import java.io.File;
+import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -124,12 +125,26 @@ public class FiletreeController implements Initializable, Controller {
String name = file.getName();
String ext = (name.substring(file.getName().lastIndexOf(".") + 1, file.getName().length()));
- if ("java".equals(ext))
- createExtension(name, java, parent);
- else if ("md".equals(ext))
- createExtension(name, md, parent);
- else
- createExtension(name, placeholder, parent);
+ try {
+ createExtension(name, getIconForFile(file), parent);
+ } catch (Exception e) {
+ System.err.println("ICON NOT FOUND: " + file.getPath());
+ }
+
+ // if ("java".equals(ext))
+ // createExtension(name, java, parent);
+ // else if ("md".equals(ext))
+ // createExtension(name, md, parent);
+ // else
+ // createExtension(name, placeholder, parent);
+ }
+
+ private Image getIconForFile(File file) throws IOException {
+ String mimeType = Files.probeContentType(file.toPath()).replace('/', '-');
+ String iconPath = (mimeType != null)
+ ? "/graphics/filetreeicons/" + mimeType + ".png"
+ : "/graphics/filetreeicons/file.png";
+ return new Image(getClass().getResourceAsStream(iconPath));
}
private void createExtension(String name, Image image, CheckBoxTreeItem parent) {
diff --git a/src/main/java/app/events/Event.java b/src/main/java/app/events/Event.java
index 2438df1..836c2df 100644
--- a/src/main/java/app/events/Event.java
+++ b/src/main/java/app/events/Event.java
@@ -3,4 +3,4 @@ package app.events;
/**
* Base class for any type of event of the eventbus
*/
-abstract class Event {}
+public abstract class Event {}
diff --git a/src/main/java/app/service/LanguageOperations.java b/src/main/java/app/service/LanguageOperations.java
index 32c22cd..c3e2343 100644
--- a/src/main/java/app/service/LanguageOperations.java
+++ b/src/main/java/app/service/LanguageOperations.java
@@ -13,7 +13,7 @@ import app.model.ProgrammingLanguage;
* Common static operations that can be executed on any class
* that implements {@link app.model.ProgrammingLanguage ProgrammingLanguage}
*/
-public class LanguageOperations {
+public final class LanguageOperations {
/**
* Use a matcher to find the styleclass of the next match
diff --git a/src/main/resources/fxml/Main.fxml b/src/main/resources/fxml/Main.fxml
index d7bc4ff..a8fadb9 100644
--- a/src/main/resources/fxml/Main.fxml
+++ b/src/main/resources/fxml/Main.fxml
@@ -10,7 +10,8 @@
prefHeight="400"
xmlns="http://javafx.com/javafx/8.0.65"
xmlns:fx="http://javafx.com/fxml/1"
- fx:controller="app.MainController">
+ fx:controller="app.MainController"
+ fx:id="root">
diff --git a/src/test/java/app/FxTestTemplate.java b/src/test/java/app/FxTestTemplate.java
new file mode 100644
index 0000000..13886ee
--- /dev/null
+++ b/src/test/java/app/FxTestTemplate.java
@@ -0,0 +1,44 @@
+package app;
+
+import javafx.scene.Node;
+import javafx.stage.Stage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.testfx.api.FxToolkit;
+import org.testfx.framework.junit5.ApplicationTest;
+import org.testfx.util.WaitForAsyncUtils;
+
+import java.util.concurrent.TimeoutException;
+
+public class FxTestTemplate extends ApplicationTest {
+
+ private Stage stage;
+
+ @BeforeEach
+ public void runAppToTests() throws Exception {
+ FxToolkit.registerPrimaryStage();
+ FxToolkit.setupApplication(Main::new);
+ FxToolkit.showStage();
+ WaitForAsyncUtils.waitForFxEvents(100);
+ }
+
+ @AfterEach
+ public void stopApp() throws TimeoutException {
+ FxToolkit.cleanupStages();
+ }
+
+ @Override
+ public void start(Stage primaryStage){
+ this.stage = primaryStage;
+ primaryStage.toFront();
+ }
+
+ public Stage getStage() {
+ return stage;
+ }
+
+ public T find(final String query) {
+ /** TestFX provides many operations to retrieve elements from the loaded GUI. */
+ return lookup(query).query();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/app/MainTest.java b/src/test/java/app/MainTest.java
index 9d0e293..1030f64 100644
--- a/src/test/java/app/MainTest.java
+++ b/src/test/java/app/MainTest.java
@@ -1,16 +1,74 @@
package app;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
-import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.platform.commons.annotation.Testable;
-public class MainTest {
+import app.controllers.*;
- @Test
- @DisplayName("Temp Test")
- public void tempTest() {
- assertEquals(1, 1);
- }
-
-}
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javafx.scene.layout.BorderPane;
+
+import app.testing.FxTestTemplate;
+
+
+@Testable
+public class MainTest extends FxTestTemplate {
+
+ @Test
+ @DisplayName("Check that the stage title is correct")
+ public void should_have_stage_title() {
+ assertEquals("Banana Editor", this.getStage().getTitle());
+ }
+
+ @Test
+ @Order(1)
+ @DisplayName("Check that the stage has an icon")
+ public void should_have_stage_icon() {
+ assertEquals(1, this.getStage().getIcons().size());
+ }
+
+
+ @Test
+ @Order(2)
+ @DisplayName("Check that the root element is present")
+ public void should_have_root() {
+ BorderPane app = (BorderPane) find("#root");
+ assertNotNull(app);
+ }
+
+ @Test
+ @Order(3)
+ @DisplayName("Check that all subcontrollers are present")
+ public void should_have_subcontrollers() {
+ this
+ .getMainController()
+ .getInnerControllers()
+ .forEach((Controller controller) -> assertNotNull(controller));
+ }
+
+ @Test
+ @DisplayName("Check that the scene is correct")
+ public void should_have_scene() throws IOException {
+ assertNotNull(this.getStage().getScene());
+ }
+
+ @Test
+ @DisplayName("Check that the CSS is set")
+ public void should_have_css() {
+ List expectedCSS =
+ List.of("/styling/themes/monokai.css", "/styling/languages/java.css")
+ .stream()
+ .map(p -> getClass().getResource(p).toExternalForm())
+ .collect(Collectors.toList());
+ assertEquals(expectedCSS, this.getStage().getScene().getStylesheets());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/app/controllers/EditorControllerTest.java b/src/test/java/app/controllers/EditorControllerTest.java
new file mode 100644
index 0000000..aaaf10e
--- /dev/null
+++ b/src/test/java/app/controllers/EditorControllerTest.java
@@ -0,0 +1,238 @@
+package app.controllers;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.fxmisc.richtext.CodeArea;
+import org.fxmisc.richtext.model.StyleSpans;
+
+import com.google.common.eventbus.EventBus;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import app.testing.FxTestTemplate;
+import app.model.Model;
+import app.model.ProgrammingLanguage;
+import app.service.LanguageOperations;
+import app.events.CopyEvent;
+import app.events.CutEvent;
+import app.events.FileSelectedEvent;
+import app.events.LanguageChangedEvent;
+import app.events.PasteEvent;
+import app.events.RedoEvent;
+import app.events.ToggleCommentEvent;
+import app.events.ToggleWrapTextEvent;
+import app.events.UndoEvent;
+
+@ExtendWith(MockitoExtension.class)
+public class EditorControllerTest extends FxTestTemplate {
+
+
+ @Captor
+ private ArgumentCaptor captor;
+
+ @Mock
+ private CodeArea editor;
+
+ private EventBus eventBus;
+
+ @InjectMocks
+ private EditorController controller;
+
+ private String mockContent = """
+ class HelloWorld {
+ private String message = "Hello world";
+
+ public String getMessage() {
+ return message;
+ }
+ }
+ """;
+
+ private String mockLine = "private String message = \"Hello world\";";
+
+ @BeforeEach
+ public void insertEventBus() {
+ this.eventBus = new EventBus();
+ this.controller.setEventBus(eventBus);
+ }
+
+ @Test
+ @DisplayName("Test handling of FileSelectedEvent with a real file")
+ public void testFileSelectedEventWithRealFile() throws IOException {
+
+ String resourcePath = "/testfile.txt";
+ String filePath = getClass().getResource(resourcePath).getPath();
+ File file = new File(filePath);
+ List content =
+ Files.readAllLines(file.toPath())
+ .stream()
+ .map(s -> s + "\n")
+ .collect(Collectors.toList());
+
+ eventBus.post(new FileSelectedEvent(filePath));
+ verify(editor, times(content.size())).appendText(captor.capture());
+
+ assertEquals(content, captor.getAllValues());
+ }
+
+ @Test
+ @DisplayName("Test handling of FileSelectedEvent with a file that doesn't exist")
+ public void testFileSelectedEventWithUnrealFile() throws IOException {
+
+ String brokenFilePath = "/doesNotExist.txt";
+ eventBus.post(new FileSelectedEvent(brokenFilePath));
+
+ verify(editor, never()).clear();
+ }
+
+ @Test
+ @DisplayName("Test handling of LanguageChangedEvent")
+ public void testLanguageChangedEvent(){
+
+ when(editor.getText()).thenReturn(mockContent);
+
+ try (MockedStatic mocked = mockStatic(LanguageOperations.class)) {
+ mocked.when(() -> LanguageOperations.syntaxHighlight(anyString(), any()))
+ .thenReturn(StyleSpans.singleton(null, 0));
+
+ eventBus.post(new LanguageChangedEvent("markdown"));
+ mocked.verify(() -> LanguageOperations.syntaxHighlight(anyString(), any()));
+ }
+ }
+
+ @Test
+ @DisplayName("Test handling of ToggleCommentEvent when not selected")
+ public void testToggleCommentEventNotSelect(){
+
+ ProgrammingLanguage lang = mock(ProgrammingLanguage.class);
+ when(editor.getSelectedText()).thenReturn("");
+ when(editor.getText(anyInt())).thenReturn(mockLine);
+
+ try (MockedStatic mocked = mockStatic(Model.class)) {
+ mocked.when(() -> Model.getLanguage()).thenReturn(lang);
+
+ when(lang.isCommentedLine(anyString())).thenReturn(false);
+ eventBus.post(new ToggleCommentEvent());
+ verify(lang).commentLine(anyString());
+
+ when(lang.isCommentedLine(anyString())).thenReturn(true);
+ eventBus.post(new ToggleCommentEvent());
+ verify(lang).unCommentLine(anyString());
+ }
+ }
+
+ @Test
+ @DisplayName("Test handling of ToggleCommentEvent when selected")
+ public void testToggleCommentEventSelect(){
+
+ ProgrammingLanguage lang = mock(ProgrammingLanguage.class);
+ when(editor.getSelectedText()).thenReturn("Selected Text");
+
+ try (MockedStatic mocked = mockStatic(Model.class)) {
+ mocked.when(() -> Model.getLanguage()).thenReturn(lang);
+
+ when(lang.isCommentedSelection(anyString())).thenReturn(false);
+ eventBus.post(new ToggleCommentEvent());
+ verify(lang).commentSelection(anyString());
+
+ when(lang.isCommentedSelection(anyString())).thenReturn(true);
+ eventBus.post(new ToggleCommentEvent());
+ verify(lang).unCommentSelection(anyString());
+ }
+ }
+
+ @Test
+ @DisplayName("Test handling of ToggleWrapTextEvent")
+ public void testToggleWrapTextEvent(){
+ eventBus.post(new ToggleWrapTextEvent(true));
+ verify(editor).setWrapText(true);
+ eventBus.post(new ToggleWrapTextEvent(false));
+ verify(editor).setWrapText(false);
+ }
+
+ @Test
+ @DisplayName("Test handling of UndoEvent")
+ public void testUndoEvent(){
+ when(editor.isFocused()).thenReturn(true);
+ eventBus.post(new UndoEvent());
+ verify(editor, times(1)).undo();
+
+ when(editor.isFocused()).thenReturn(false);
+ eventBus.post(new UndoEvent());
+ // Should not have been called one more time
+ verify(editor, times(1)).undo();
+ }
+
+ @Test
+ @DisplayName("Test handling of RedoEvent")
+ public void testRedoEvent(){
+ when(editor.isFocused()).thenReturn(true);
+ eventBus.post(new RedoEvent());
+ verify(editor, times(1)).redo();
+
+ when(editor.isFocused()).thenReturn(false);
+ eventBus.post(new RedoEvent());
+ verify(editor, times(1)).redo();
+ }
+
+ @Test
+ @DisplayName("Test handling of CopyEvent")
+ public void testCopyEvent(){
+ when(editor.isFocused()).thenReturn(true);
+ eventBus.post(new CopyEvent());
+ verify(editor, times(1)).copy();
+
+ when(editor.isFocused()).thenReturn(false);
+ eventBus.post(new CopyEvent());
+ verify(editor, times(1)).copy();
+ }
+
+ @Test
+ @DisplayName("Test handling of CutEvent")
+ public void testCutEvent(){
+ when(editor.isFocused()).thenReturn(true);
+ eventBus.post(new CutEvent());
+ verify(editor, times(1)).cut();
+
+ when(editor.isFocused()).thenReturn(false);
+ eventBus.post(new CutEvent());
+ verify(editor, times(1)).cut();
+ }
+
+ @Test
+ @DisplayName("Test handling of PasteEvent")
+ public void testPasteEvent(){
+ when(editor.isFocused()).thenReturn(true);
+ eventBus.post(new PasteEvent());
+ verify(editor, times(1)).paste();
+
+ when(editor.isFocused()).thenReturn(false);
+ eventBus.post(new PasteEvent());
+ verify(editor, times(1)).paste();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/app/events/FileSaveStateChangedEventTest.java b/src/test/java/app/events/FileSaveStateChangedEventTest.java
new file mode 100644
index 0000000..cdb9ff1
--- /dev/null
+++ b/src/test/java/app/events/FileSaveStateChangedEventTest.java
@@ -0,0 +1,18 @@
+package app.events;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import app.model.Model;
+import app.testing.EventTestTemplate;
+
+public class FileSaveStateChangedEventTest extends EventTestTemplate {
+
+ @Test
+ @DisplayName("Check that model gets changed on constructor")
+ public void checkModel() {
+ new FileSaveStateChangedEvent(true);
+ this.mockModel.verify(() -> Model.setFileIsSaved(true));
+ }
+
+}
diff --git a/src/test/java/app/testing/ControllerTestTemplate.java b/src/test/java/app/testing/ControllerTestTemplate.java
new file mode 100644
index 0000000..1c8a021
--- /dev/null
+++ b/src/test/java/app/testing/ControllerTestTemplate.java
@@ -0,0 +1,5 @@
+package app.testing;
+
+public class ControllerTestTemplate extends FxTestTemplate {
+
+}
\ No newline at end of file
diff --git a/src/test/java/app/testing/EventTestTemplate.java b/src/test/java/app/testing/EventTestTemplate.java
new file mode 100644
index 0000000..01a1985
--- /dev/null
+++ b/src/test/java/app/testing/EventTestTemplate.java
@@ -0,0 +1,25 @@
+package app.testing;
+
+import static org.mockito.Mockito.mockStatic;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.mockito.MockedStatic;
+
+import app.model.Model;
+
+public class EventTestTemplate extends FxTestTemplate {
+
+ public MockedStatic mockModel;
+
+ @BeforeEach
+ public void openModel() {
+ mockModel = mockStatic(Model.class);
+ }
+
+ @AfterEach
+ public void closeModel() {
+ mockModel.close();
+ }
+
+}
diff --git a/src/test/java/app/testing/FxTestTemplate.java b/src/test/java/app/testing/FxTestTemplate.java
new file mode 100644
index 0000000..e024a8d
--- /dev/null
+++ b/src/test/java/app/testing/FxTestTemplate.java
@@ -0,0 +1,56 @@
+package app.testing;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Node;
+import javafx.stage.Stage;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.testfx.api.FxToolkit;
+import org.testfx.framework.junit5.ApplicationTest;
+import org.testfx.util.WaitForAsyncUtils;
+
+import java.util.concurrent.TimeoutException;
+
+import app.Main;
+import app.MainController;
+
+public class FxTestTemplate extends ApplicationTest {
+
+ private Stage stage;
+ private Application application;
+
+ @BeforeEach
+ public void runAppToTests() throws Exception {
+ FxToolkit.registerPrimaryStage();
+ this.application = FxToolkit.setupApplication(Main::new);
+
+ FxToolkit.showStage();
+ WaitForAsyncUtils.waitForFxEvents(100);
+ }
+
+ @AfterEach
+ public void stopApp() throws TimeoutException {
+ FxToolkit.cleanupStages();
+ FxToolkit.cleanupApplication(this.application);
+ }
+
+ @Override
+ public void start(Stage primaryStage){
+ this.stage = primaryStage;
+ primaryStage.toFront();
+ }
+
+ public Stage getStage() {
+ return stage;
+ }
+
+ public MainController getMainController() {
+ return ((FXMLLoader) this.stage.getScene().getUserData()).getController();
+ }
+
+ public T find(final String query) {
+ /** TestFX provides many operations to retrieve elements from the loaded GUI. */
+ return lookup(query).query();
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/testfile.txt b/src/test/resources/testfile.txt
new file mode 100644
index 0000000..2528c5e
--- /dev/null
+++ b/src/test/resources/testfile.txt
@@ -0,0 +1,3 @@
+public class testfile {
+
+}