diff --git a/pom.xml b/pom.xml index f65fd07..a9f5520 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,14 @@ 6.0.1 test + + + + org.testfx + testfx-junit5 + 4.0.18 + test + diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java new file mode 100644 index 0000000..e67e360 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java @@ -0,0 +1,18 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.service.event; + +/** + * Interface representing an event channel. + * + *

Used to separate event types into groups.

+ * + *

Decreases coupling and enables testing of event types.

+ * */ +public interface EventChannel { + + /** + * Getter method for enum name. + * + * @return String name of enum. + * */ + String getName(); +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventData.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventData.java index 70465f6..b35f098 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventData.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventData.java @@ -4,10 +4,10 @@ * Data object sent through the event system by the {@link EventManager}. * * @param the type of data this object represents. - * @param eventType the event type this object represents. + * @param channel the event type this object represents. * @param data the data this object contains. * */ -public record EventData(EventType eventType, T data) { +public record EventData(EventChannel channel, T data) { } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java index a2bf1b5..a4a2bad 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java @@ -1,9 +1,6 @@ package edu.ntnu.idi.idatt2003.g40.mappe.service.event; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Event broker in the pub/sub model. @@ -26,19 +23,19 @@ public final class EventManager { *

Used to identify which subscribers to fire the event towards.

* */ - private final Map> subscriberMap = - new EnumMap<>(EventType.class); + private final Map> subscriberMap = + new HashMap<>(); /** * Method for adding a subscriber to the subscriber map given an event type. * - * @param subscriber the {@link EventSubscriber} object to add. - * @param eventType the {@link EventType} this subscriber should subscribe to. + * @param subscriber the {@link EventSubscriber} object to add. + * @param eventChannel the {@link EventChannel} type this subscriber should subscribe to. * * */ - public void addSubscriber(final EventSubscriber subscriber, final EventType eventType) { - subscriberMap.computeIfAbsent(eventType, k -> new ArrayList<>()).add(subscriber); + public void addSubscriber(final EventSubscriber subscriber, final EventChannel eventChannel) { + subscriberMap.computeIfAbsent(eventChannel, k -> new ArrayList<>()).add(subscriber); } /** @@ -47,11 +44,19 @@ public void addSubscriber(final EventSubscriber subscriber, final EventType even * @param the type of event data to send. * @param data the data to send. * - * + * @throws IllegalArgumentException if no subscriber is found of the + * event type. */ - public void invokeEvent(final EventData data) { - for (EventSubscriber e : subscriberMap.get(data.eventType())) { - e.handleEvent(data); + public void invokeEvent(final EventData data) + throws IllegalArgumentException { + if (data == null || !subscriberMap.containsKey(data.channel())) { + throw new IllegalArgumentException( + "No subscriber listening to this event!" + ); + } else { + for (EventSubscriber e : subscriberMap.get(data.channel())) { + e.handleEvent(data); + } } } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java index 52a60b9..4231866 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java @@ -10,9 +10,8 @@ *
  • Other
  • * * - * */ -public enum EventType { +public enum EventType implements EventChannel { /** * Event type representing events that causes the current scene to change. @@ -23,16 +22,13 @@ public enum EventType { * @see edu.ntnu.idi.idatt2003.g40.mappe.view.ViewManager * */ - SCENE_CHANGE, + SCENE_CHANGE; /** - * Event type representing events that causes the scene to change to the previous one. - * - *

    Primarily handled by the active - * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.ViewManager} object.

    - * - * @see edu.ntnu.idi.idatt2003.g40.mappe.view.ViewManager - * - */ - SCENE_BACK, + * {@inheritDoc} + * */ + @Override + public String getName() { + return this.name(); + } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java new file mode 100644 index 0000000..58d6d0e --- /dev/null +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java @@ -0,0 +1,98 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.service.event; + +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EventManagerTest { + private enum TestEventTypes implements EventChannel { + TEST_TYPE_1, + TEST_TYPE_2, + TEST_TYPE_3; + + @Override + public String getName() { + return this.name(); + } + } + + private GenericEventPublisher testEventPublisher; + private GenericEventPublisher testEventPublisher2; + private GenericEventPublisher testEventPublisher3; + + private GenericEventSubscriber testEventSubscriber; + private GenericEventSubscriber testEventSubscriber2; + + private EventManager testEventManager; + + @BeforeEach + void setUp() { + testEventManager = new EventManager(); + + testEventSubscriber = new GenericEventSubscriber(); + testEventSubscriber2 = new GenericEventSubscriber(); + + testEventPublisher = new GenericEventPublisher(testEventManager, TestEventTypes.TEST_TYPE_1); + testEventPublisher2 = new GenericEventPublisher(testEventManager, TestEventTypes.TEST_TYPE_2); + testEventPublisher3 = new GenericEventPublisher(testEventManager, TestEventTypes.TEST_TYPE_3); + + testEventManager.addSubscriber(testEventSubscriber, TestEventTypes.TEST_TYPE_1); + testEventManager.addSubscriber(testEventSubscriber2, TestEventTypes.TEST_TYPE_2); + } + + @Test + void firedEventCaughtByCorrectSubscriber() { + assertFalse(testEventSubscriber.invokedEvent); + testEventPublisher.fireEvent(); + assertTrue(testEventSubscriber.invokedEvent); + } + + @Test + void firedEventNotCaughtByIncorrectSubscriber() { + assertFalse(testEventSubscriber.invokedEvent); + testEventPublisher2.fireEvent(); + assertFalse(testEventSubscriber.invokedEvent); + } + + @Test + void firedEventThrowsErrorWhenNoSubscribers() { + assertFalse(testEventSubscriber.invokedEvent); + assertThrows(IllegalArgumentException.class, () -> { + testEventPublisher3.fireEvent(); + }); + assertFalse(testEventSubscriber.invokedEvent); + } + + private class GenericEventPublisher implements EventPublisher { + + private final ViewData viewData; + private final EventData eventData; + private final EventManager eventManager; + + public GenericEventPublisher(final EventManager eventManager, final TestEventTypes eventType) { + viewData = new ViewData("Test"); + eventData = new EventData(eventType, viewData); + this.eventManager = eventManager; + } + + public void fireEvent() { + invoke(eventData, eventManager); + } + + @Override + public void invoke(EventData data, EventManager eventManager) { + eventManager.invokeEvent(data); + } + } + + private class GenericEventSubscriber implements EventSubscriber { + public boolean invokedEvent = false; + + @Override + public void handleEvent(EventData data) { + invokedEvent = true; + } + } +} \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java new file mode 100644 index 0000000..abf0158 --- /dev/null +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java @@ -0,0 +1,69 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; +import javafx.scene.control.Button; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; +import org.junit.jupiter.api.Test; +import org.testfx.framework.junit5.ApplicationTest; + +class ViewControllerTest extends ApplicationTest { + private EventManager testEventManager; + private GenericViewController testViewController; + private GenericViewElement testViewElement; + + @Override + public void start(Stage stage) { + testEventManager = new EventManager(); + testViewElement = new ViewControllerTest.GenericViewElement(new Pane()); + testViewController = new GenericViewController(testViewElement, testEventManager); + } + + @Test + void controllerElementSetsButtonBehavior() { + assertFalse(testViewElement.buttonPressed); + testViewElement.getInteractableButton().fire(); + assertTrue(testViewElement.buttonPressed); + } + + private class GenericViewElement extends ViewElement { + public Boolean buttonPressed = false; + private Button interactableButton; + + protected GenericViewElement(final Pane rootPane) { + super(rootPane); + } + + @Override + protected void initLayout() { + interactableButton = new Button("Click me!"); + getButtons().add(interactableButton); + } + + public Button getInteractableButton() { + return interactableButton; + } + + @Override + protected void initStyling() { } + } + + private class GenericViewController extends ViewController { + + protected GenericViewController(final ViewControllerTest.GenericViewElement viewElement, + final EventManager eventManager) + throws IllegalArgumentException { + super(viewElement, eventManager); + } + + @Override + protected void initInteractions() { + getViewElement().getInteractableButton().setOnAction(e -> { + getViewElement().buttonPressed = true; + }); + } + } +} diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java new file mode 100644 index 0000000..1f9aeb3 --- /dev/null +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java @@ -0,0 +1,108 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view; + +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.testfx.framework.junit5.ApplicationTest; + +/** + * Tests the view manager. + * + *

    Uses the testfx framework

    + * + */ +class ViewManagerTest extends ApplicationTest { + + private ViewManager testViewManager; + private EventManager testEventManager; + private ViewManagerTest.GenericViewElement genericViewElement; + + @Override + public void start(final Stage stage) { + stage.setScene(new Scene(new Pane())); + testEventManager = new EventManager(); + testViewManager = new ViewManager(stage, testEventManager); + genericViewElement = new GenericViewElement(new Pane(), "Test View"); + } + + @Test + void testViewManagerHoldsNoViewAtStart() { + Assertions.assertNull(testViewManager.getCurrentView()); + } + + @Test + void addingMultipleOfSameViewThrowsException() { + testViewManager.addView(genericViewElement); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + testViewManager.addView(genericViewElement); + }); + } + + @Test + void testViewManagerSettingCorrectView() { + testViewManager.addView(genericViewElement); + testViewManager.setScene(genericViewElement); + Assertions.assertEquals(genericViewElement, testViewManager.getCurrentView()); + } + + @Test + void testViewManagerThrowingErrorWhenSettingNonExistentView() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + testViewManager.setScene(genericViewElement); + }); + } + + @Test + void throwsErrorWhenAddingWidgetWithNoViewName() { + genericViewElement = new GenericViewElement(new Pane()); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + testViewManager.addView(genericViewElement); + }); + } + + private class GenericViewElement extends ViewElement { + public Boolean buttonPressed = false; + private Button interactableButton; + + protected GenericViewElement(final Pane rootPane, final String viewName) { + super(rootPane, viewName); + } + + protected GenericViewElement(final Pane rootPane) { + super(rootPane); + } + + @Override + protected void initLayout() { + interactableButton = new Button("Click me!"); + getButtons().add(interactableButton); + } + + public Button getInteractableButton() { + return interactableButton; + } + + @Override + protected void initStyling() { } + } + + private class GenericViewController extends ViewController { + + protected GenericViewController(final ViewManagerTest.GenericViewElement viewElement, + final EventManager eventManager) + throws IllegalArgumentException { + super(viewElement, eventManager); + } + + @Override + protected void initInteractions() { + getViewElement().getInteractableButton().setOnAction(e -> { + getViewElement().buttonPressed = true; + }); + } + } +}