Repository: capricorn86/happy-dom (ts)

Issue:
Event Dispatch - handleEvent undefined
**Describe the bug**
When adding multiple event listeners to an element with option `once: true`, then dispatching that same event will cause the dispatch loop to access the listener array out of bounds because the `max` has been pinned to the previous value.

```
TypeError: Cannot read properties of undefined (reading 'handleEvent')
    at HTMLElement.dispatchEvent (D:\Projects\happ-dom-repro1\node_modules\happy-dom\lib\event\EventTarget.js:59:30)
    at HTMLElement.dispatchEvent (D:\Projects\happ-dom-repro1\node_modules\happy-dom\lib\nodes\node\Node.js:383:35)
    at file:///D:/Projects/happ-dom-repro1/index2.js:14:6
    at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:530:24)
    at async loadESM (node:internal/process/esm_loader:91:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12)
```

**To Reproduce**
Steps to reproduce the behavior:

Reproduction Code:
```js
import { Window } from 'happy-dom';

const window = new Window();
const document = window.document;

document.body.innerHTML = '<div class="container"><input type="text" name="expiry"></div>';

const eventName = 'myEvent';
const text = document.querySelector('div');
const action1 = () => console.log('1');
const action2 = () => console.log('2');
text.addEventListener(eventName, action1, { once: true });
text.addEventListener(eventName, action2, { once: true });
text.dispatchEvent(new Event(eventName));
```

**Expected behavior**
Dispatching the event does not cause an unhandled exception.

**Device:**
 - Node.js v18.12.0

**Additional context**
Link: https://github.com/capricorn86/happy-dom/blob/13bcfe77ae9a202a93ce8fddcf4a7c1b43ecde16/packages/happy-dom/src/event/EventTarget.ts#L76

Pull request description:
#805@patch: Improve event listener.
fix #805

Patch:
diff --git a/packages/happy-dom/src/event/EventTarget.ts b/packages/happy-dom/src/event/EventTarget.ts
index b2c6151c..8b602be3 100644
--- a/packages/happy-dom/src/event/EventTarget.ts
+++ b/packages/happy-dom/src/event/EventTarget.ts
@@ -35,7 +35,9 @@ export default abstract class EventTarget implements IEventTarget {
 
 		this._listeners[type] = this._listeners[type] || [];
 		this._listenerOptions[type] = this._listenerOptions[type] || [];
-
+		if (this._listeners[type].includes(listener)) {
+			return;
+		}
 		this._listeners[type].push(listener);
 		this._listenerOptions[type].push(listenerOptions);
 
@@ -168,7 +170,13 @@ export default abstract class EventTarget implements IEventTarget {
 				event._isInPassiveEventListener = false;
 
 				if (options?.once) {
+					// At this time, listeners and listenersOptions are cloned arrays. When the original value is deleted,
+					// The value corresponding to the cloned array is not deleted. So we need to delete the value in the cloned array.
+					listeners.splice(i, 1);
+					listenerOptions.splice(i, 1);
 					this.removeEventListener(event.type, listener);
+					i--;
+					max--;
 				}
 
 				if (event._immediatePropagationStopped) {


Test patch:
diff --git a/packages/happy-dom/test/event/EventTarget.test.ts b/packages/happy-dom/test/event/EventTarget.test.ts
index 1087bf01..907e03f2 100644
--- a/packages/happy-dom/test/event/EventTarget.test.ts
+++ b/packages/happy-dom/test/event/EventTarget.test.ts
@@ -41,6 +41,27 @@ describe('EventTarget', () => {
 			expect(count).toBe(1);
 		});
 
+		it('Adds an event listener and set options once and bind the same event multiple times', () => {
+			let count = 0;
+			const listener = (): void => {
+				count++;
+			};
+			const listener1 = (): void => {
+				count++;
+			};
+			const dispatchedEvent = new Event(EVENT_TYPE);
+			eventTarget.addEventListener(EVENT_TYPE, listener, { once: true });
+			eventTarget.addEventListener(EVENT_TYPE, listener1, { once: true });
+			eventTarget.dispatchEvent(dispatchedEvent);
+			expect(count).toBe(2);
+			eventTarget.dispatchEvent(dispatchedEvent);
+			expect(count).toBe(2);
+			eventTarget.addEventListener(EVENT_TYPE, listener, { once: true });
+			eventTarget.addEventListener(EVENT_TYPE, listener, { once: true });
+			eventTarget.dispatchEvent(dispatchedEvent);
+			expect(count).toBe(3);
+		});
+
 		it('Adds a custom event listener and triggers it when calling dispatchEvent().', () => {
 			let recievedEvent: CustomEvent = null;
 			const DETAIL = {};