Repository: capricorn86/happy-dom
Pull request #227 (2021-03-05 11:59:38)

#226@minor: Adds support for EventListener interface to EventTarget.a…
…ddEventListener() and EventTarget.removeEventListener(), which makes it possible to add and remove event listeners using and object with handleEvent as property.


Patch:
diff --git a/packages/happy-dom/src/event/ErrorEvent.ts b/packages/happy-dom/src/event/ErrorEvent.ts
deleted file mode 100644
index 500c947d..00000000
--- a/packages/happy-dom/src/event/ErrorEvent.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import Event from './Event';
-
-export default class ErrorEvent extends Event {
-	public error: Error = null;
-}
diff --git a/packages/happy-dom/src/event/EventTarget.ts b/packages/happy-dom/src/event/EventTarget.ts
index 72221d8b..a26c55bc 100644
--- a/packages/happy-dom/src/event/EventTarget.ts
+++ b/packages/happy-dom/src/event/EventTarget.ts
@@ -1,3 +1,4 @@
+import IEventListener from './IEventListener';
 import Event from './Event';
 import IEventTarget from './IEventTarget';
 
@@ -5,7 +6,9 @@ import IEventTarget from './IEventTarget';
  * Handles events.
  */
 export default abstract class EventTarget implements IEventTarget {
-	protected readonly _listeners: { [k: string]: ((event: Event) => void)[] } = {};
+	protected readonly _listeners: {
+		[k: string]: (((event: Event) => void) | IEventListener)[];
+	} = {};
 
 	/**
 	 * Adds an event listener.
@@ -13,7 +16,7 @@ export default abstract class EventTarget implements IEventTarget {
 	 * @param type Event type.
 	 * @param listener Listener.
 	 */
-	public addEventListener(type: string, listener: (event: Event) => void): void {
+	public addEventListener(type: string, listener: ((event: Event) => void) | IEventListener): void {
 		this._listeners[type] = this._listeners[type] || [];
 		this._listeners[type].push(listener);
 	}
@@ -24,7 +27,10 @@ export default abstract class EventTarget implements IEventTarget {
 	 * @param type Event type.
 	 * @param listener Listener.
 	 */
-	public removeEventListener(type: string, listener: (event: Event) => void): void {
+	public removeEventListener(
+		type: string,
+		listener: ((event: Event) => void) | IEventListener
+	): void {
 		if (this._listeners[type]) {
 			const index = this._listeners[type].indexOf(listener);
 			if (index !== -1) {
@@ -48,7 +54,11 @@ export default abstract class EventTarget implements IEventTarget {
 
 		if (this._listeners[event.type]) {
 			for (const listener of this._listeners[event.type]) {
-				listener(event);
+				if ((<IEventListener>listener).handleEvent) {
+					(<IEventListener>listener).handleEvent(event);
+				} else {
+					(<(event: Event) => void>listener)(event);
+				}
 				if (event._immediatePropagationStopped) {
 					return !(event.cancelable && event.defaultPrevented);
 				}
diff --git a/packages/happy-dom/src/event/IEventListener.ts b/packages/happy-dom/src/event/IEventListener.ts
new file mode 100644
index 00000000..b731998d
--- /dev/null
+++ b/packages/happy-dom/src/event/IEventListener.ts
@@ -0,0 +1,13 @@
+import Event from './Event';
+
+/**
+ * Event listener.
+ */
+export default interface IEventListener {
+	/**
+	 * Handles event.
+	 *
+	 * @param type Event type.
+	 */
+	handleEvent(event: Event): void;
+}
diff --git a/packages/happy-dom/src/event/IEventTarget.ts b/packages/happy-dom/src/event/IEventTarget.ts
index 21001d07..b653d3f7 100644
--- a/packages/happy-dom/src/event/IEventTarget.ts
+++ b/packages/happy-dom/src/event/IEventTarget.ts
@@ -1,3 +1,4 @@
+import IEventListener from './IEventListener';
 import Event from './Event';
 
 /**
@@ -10,7 +11,7 @@ export default interface IEventTarget {
 	 * @param type Event type.
 	 * @param listener Listener.
 	 */
-	addEventListener(type: string, listener: (event: Event) => void): void;
+	addEventListener(type: string, listener: ((event: Event) => void) | IEventListener): void;
 
 	/**
 	 * Adds an event listener.
@@ -18,7 +19,7 @@ export default interface IEventTarget {
 	 * @param type Event type.
 	 * @param listener Listener.
 	 */
-	removeEventListener(type: string, listener: (event: Event) => void): void;
+	removeEventListener(type: string, listener: ((event: Event) => void) | IEventListener): void;
 
 	/**
 	 * Dispatches an event.
diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts
index e3563abc..9c5f35e6 100644
--- a/packages/happy-dom/src/index.ts
+++ b/packages/happy-dom/src/index.ts
@@ -1,4 +1,28 @@
 import Window from './window/Window';
+import DataTransfer from './event/DataTransfer';
+import DataTransferItem from './event/DataTransferItem';
+import DataTransferItemList from './event/DataTransferItemList';
+import Event from './event/Event';
+import EventTarget from './event/EventTarget';
+import IEventInit from './event/IEventInit';
+import IEventListener from './event/IEventListener';
+import IUIEventInit from './event/IUIEventInit';
+import UIEvent from './event/UIEvent';
+import ErrorEvent from './event/events/ErrorEvent';
+import FocusEvent from './event/events/FocusEvent';
+import ICustomEventInit from './event/events/ICustomEventInit';
+import IErrorEventInit from './event/events/IErrorEventInit';
+import IFocusEventInit from './event/events/IFocusEventInit';
+import IInputEventInit from './event/events/IInputEventInit';
+import IKeyboardEventInit from './event/events/IKeyboardEventInit';
+import IMouseEventInit from './event/events/IMouseEventInit';
+import IProgressEvent from './event/events/IProgressEvent';
+import IWheelEventInit from './event/events/IWheelEventInit';
+import InputEvent from './event/events/InputEvent';
+import KeyboardEvent from './event/events/KeyboardEvent';
+import MouseEvent from './event/events/MouseEvent';
+import ProgressEvent from './event/events/ProgressEvent';
+import WheelEvent from './event/events/WheelEvent';
 import AsyncWindow from './window/AsyncWindow';
 import DOMParser from './dom-parser/DOMParser';
 import Document from './nodes/document/Document';
@@ -52,6 +76,30 @@ import CSSStyleSheet from './css/CSSStyleSheet';
 export {
 	AsyncWindow,
 	Window,
+	DataTransfer,
+	DataTransferItem,
+	DataTransferItemList,
+	Event,
+	EventTarget,
+	IEventInit,
+	IEventListener,
+	IUIEventInit,
+	UIEvent,
+	ErrorEvent,
+	FocusEvent,
+	ICustomEventInit,
+	IErrorEventInit,
+	IFocusEventInit,
+	IInputEventInit,
+	IKeyboardEventInit,
+	IMouseEventInit,
+	IProgressEvent,
+	IWheelEventInit,
+	InputEvent,
+	KeyboardEvent,
+	MouseEvent,
+	ProgressEvent,
+	WheelEvent,
 	DOMParser,
 	Document,
 	IDocument,
diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts
index cc82df46..79dd9ce1 100644
--- a/packages/happy-dom/src/nodes/document/Document.ts
+++ b/packages/happy-dom/src/nodes/document/Document.ts
@@ -435,10 +435,10 @@ export default class Document extends Node implements IDocument {
 		this._isFirstWriteAfterOpen = true;
 
 		for (const eventType of Object.keys(this._listeners)) {
-			const callbacks = this._listeners[eventType];
-			if (callbacks) {
-				for (const callback of callbacks) {
-					this.removeEventListener(eventType, callback);
+			const listeners = this._listeners[eventType];
+			if (listeners) {
+				for (const listener of listeners) {
+					this.removeEventListener(eventType, listener);
 				}
 			}
 		}
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index 995f5821..d9d57985 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -48,7 +48,7 @@ import DataTransferItem from '../event/DataTransferItem';
 import DataTransferItemList from '../event/DataTransferItemList';
 import InputEvent from '../event/events/InputEvent';
 import UIEvent from '../event/UIEvent';
-import ErrorEvent from '../event/ErrorEvent';
+import ErrorEvent from '../event/events/ErrorEvent';
 import Screen from '../screen/Screen';
 import AsyncTaskManager from './AsyncTaskManager';
 import IResponse from './IResponse';
@@ -107,6 +107,7 @@ export default class Window extends EventTarget implements NodeJS.Global {
 	public InputEvent = InputEvent;
 	public ErrorEvent = ErrorEvent;
 	public ProgressEvent = ProgressEvent;
+	public EventTarget = EventTarget;
 	public DataTransfer = DataTransfer;
 	public DataTransferItem = DataTransferItem;
 	public DataTransferItemList = DataTransferItemList;


Test patch:
diff --git a/packages/happy-dom/test/event/EventTarget.test.ts b/packages/happy-dom/test/event/EventTarget.test.ts
index a521a50a..861a681c 100644
--- a/packages/happy-dom/test/event/EventTarget.test.ts
+++ b/packages/happy-dom/test/event/EventTarget.test.ts
@@ -26,7 +26,7 @@ describe('EventTarget', () => {
 			expect(recievedEvent.currentTarget).toBe(eventTarget);
 		});
 
-		test('Triggers a custom event and triggers it when calling dispatchEvent().', () => {
+		test('Adds a custom event listener and triggers it when calling dispatchEvent().', () => {
 			let recievedEvent: CustomEvent = null;
 			const DETAIL = {};
 			const listener = (event: CustomEvent): void => {
@@ -40,6 +40,21 @@ describe('EventTarget', () => {
 			expect(recievedEvent.target).toBe(eventTarget);
 			expect(recievedEvent.currentTarget).toBe(eventTarget);
 		});
+
+		test('Adds an event listener using object with handleEvent as property and triggers it when calling dispatchEvent().', () => {
+			let recievedEvent: CustomEvent = null;
+			const listener = {
+				handleEvent: (event: CustomEvent): void => {
+					recievedEvent = event;
+				}
+			};
+			const dispatchedEvent = new Event(EVENT_TYPE);
+			eventTarget.addEventListener(EVENT_TYPE, listener);
+			eventTarget.dispatchEvent(dispatchedEvent);
+			expect(recievedEvent).toBe(dispatchedEvent);
+			expect(recievedEvent.target).toBe(eventTarget);
+			expect(recievedEvent.currentTarget).toBe(eventTarget);
+		});
 	});
 
 	describe('removeEventListener()', () => {