Repository: capricorn86/happy-dom
Pull request #763 (2023-02-18 14:21:00)

#700@minor: Add event listener options.
rel #700 


Patch:
diff --git a/packages/happy-dom/src/event/EventTarget.ts b/packages/happy-dom/src/event/EventTarget.ts
index 7eb65c80..703a1cbe 100644
--- a/packages/happy-dom/src/event/EventTarget.ts
+++ b/packages/happy-dom/src/event/EventTarget.ts
@@ -1,6 +1,7 @@
 import IEventListener from './IEventListener';
 import Event from './Event';
 import IEventTarget from './IEventTarget';
+import IEventListenerOptions from './IEventListenerOptions';
 
 /**
  * Handles events.
@@ -9,16 +10,28 @@ export default abstract class EventTarget implements IEventTarget {
 	public readonly _listeners: {
 		[k: string]: (((event: Event) => void) | IEventListener)[];
 	} = {};
+	public readonly _listenerOptions: {
+		[k: string]: (IEventListenerOptions | null)[];
+	} = {};
 
 	/**
 	 * Adds an event listener.
 	 *
 	 * @param type Event type.
 	 * @param listener Listener.
+	 * @param options An object that specifies characteristics about the event listener.(currently only once)
+	 * @param options.once
 	 */
-	public addEventListener(type: string, listener: ((event: Event) => void) | IEventListener): void {
+	public addEventListener(
+		type: string,
+		listener: ((event: Event) => void) | IEventListener,
+		options?: IEventListenerOptions
+	): void {
 		this._listeners[type] = this._listeners[type] || [];
+		this._listenerOptions[type] = this._listenerOptions[type] || [];
+
 		this._listeners[type].push(listener);
+		this._listenerOptions[type].push(options || null);
 	}
 
 	/**
@@ -35,6 +48,7 @@ export default abstract class EventTarget implements IEventTarget {
 			const index = this._listeners[type].indexOf(listener);
 			if (index !== -1) {
 				this._listeners[type].splice(index, 1);
+				this._listenerOptions[type].splice(index, 1);
 			}
 		}
 	}
@@ -59,12 +73,20 @@ export default abstract class EventTarget implements IEventTarget {
 		}
 
 		if (this._listeners[event.type]) {
-			for (const listener of this._listeners[event.type]) {
+			for (let i = 0, max = this._listeners[event.type].length; i < max; i++) {
+				const listener = this._listeners[event.type][i];
+				const options = this._listenerOptions[event.type][i];
+
 				if ((<IEventListener>listener).handleEvent) {
 					(<IEventListener>listener).handleEvent(event);
 				} else {
 					(<(event: Event) => void>listener).call(this, event);
 				}
+
+				if (options?.once) {
+					this.removeEventListener(event.type, listener);
+				}
+
 				if (event._immediatePropagationStopped) {
 					return !(event.cancelable && event.defaultPrevented);
 				}
diff --git a/packages/happy-dom/src/event/IEventListenerOptions.ts b/packages/happy-dom/src/event/IEventListenerOptions.ts
new file mode 100644
index 00000000..81e411e0
--- /dev/null
+++ b/packages/happy-dom/src/event/IEventListenerOptions.ts
@@ -0,0 +1,8 @@
+export default interface IEventListenerOptions {
+	once?: boolean;
+
+	// TODO: Not implemented yet.
+	capture?: boolean;
+	passive?: boolean;
+	signal?: unknown;
+}


Test patch:
diff --git a/packages/happy-dom/test/event/EventTarget.test.ts b/packages/happy-dom/test/event/EventTarget.test.ts
index 5031d0f1..e9571b7d 100644
--- a/packages/happy-dom/test/event/EventTarget.test.ts
+++ b/packages/happy-dom/test/event/EventTarget.test.ts
@@ -29,6 +29,18 @@ describe('EventTarget', () => {
 			expect(recievedEvent.currentTarget).toBe(eventTarget);
 		});
 
+		it('Adds an event listener and set options once', () => {
+			let count = 0;
+			const listener = (): void => {
+				count++;
+			};
+			const dispatchedEvent = new Event(EVENT_TYPE);
+			eventTarget.addEventListener(EVENT_TYPE, listener, { once: true });
+			eventTarget.dispatchEvent(dispatchedEvent);
+			eventTarget.dispatchEvent(dispatchedEvent);
+			expect(count).toBe(1);
+		});
+
 		it('Adds a custom event listener and triggers it when calling dispatchEvent().', () => {
 			let recievedEvent: CustomEvent = null;
 			const DETAIL = {};