Skip to content

Commit

Permalink
Feat: Updated event system to work with channels.
Browse files Browse the repository at this point in the history
  • Loading branch information
tommyah committed May 11, 2026
1 parent b309fd9 commit da76e31
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 28 deletions.
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
<version>6.0.1</version>
<scope>test</scope>
</dependency>

<!-- JavaFX testing -->
<dependency>
<groupId>org.testfx</groupId>
<artifactId>testfx-junit5</artifactId>
<version>4.0.18</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service.event;

/**
* Interface representing an event channel.
*
* <p>Used to separate event types into groups.</p>
*
* <p>Decreases coupling and enables testing of event types.</p>
* */
public interface EventChannel {

/**
* Getter method for enum name.
*
* @return String name of enum.
* */
String getName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
* Data object sent through the event system by the {@link EventManager}.
*
* @param <T> 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<T>(EventType eventType, T data) {
public record EventData<T>(EventChannel channel, T data) {

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -26,19 +23,19 @@ public final class EventManager {
* <p>Used to identify which subscribers to fire the event towards.</p>
*
*/
private final Map<EventType, List<EventSubscriber>> subscriberMap =
new EnumMap<>(EventType.class);
private final Map<EventChannel, List<EventSubscriber>> 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);
}

/**
Expand All @@ -47,11 +44,19 @@ public void addSubscriber(final EventSubscriber subscriber, final EventType even
* @param <T> 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 <T> void invokeEvent(final EventData<T> data) {
for (EventSubscriber e : subscriberMap.get(data.eventType())) {
e.handleEvent(data);
public <T> void invokeEvent(final EventData<T> 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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
* <li>Other</li>
* </ul>
*
*
*/
public enum EventType {
public enum EventType implements EventChannel {

/**
* Event type representing events that causes the current scene to change.
Expand All @@ -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.
*
* <p>Primarily handled by the active
* {@link edu.ntnu.idi.idatt2003.g40.mappe.view.ViewManager} object.</p>
*
* @see edu.ntnu.idi.idatt2003.g40.mappe.view.ViewManager
*
*/
SCENE_BACK,
* {@inheritDoc}
* */
@Override
public String getName() {
return this.name();
}
}
Original file line number Diff line number Diff line change
@@ -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<ViewData> eventData;
private final EventManager eventManager;

public GenericEventPublisher(final EventManager eventManager, final TestEventTypes eventType) {
viewData = new ViewData("Test");
eventData = new EventData<ViewData>(eventType, viewData);
this.eventManager = eventManager;
}

public void fireEvent() {
invoke(eventData, eventManager);
}

@Override
public <T> void invoke(EventData<T> data, EventManager eventManager) {
eventManager.invokeEvent(data);
}
}

private class GenericEventSubscriber implements EventSubscriber {
public boolean invokedEvent = false;

@Override
public <T> void handleEvent(EventData<T> data) {
invokedEvent = true;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Pane> {
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<ViewControllerTest.GenericViewElement> {

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;
});
}
}
}
Loading

0 comments on commit da76e31

Please sign in to comment.