Repository: capricorn86/happy-dom
Pull request #341 (2022-01-25 21:32:42)

#324@patch: Fixes issue with events not bubbling up to Window from Do…
…cument. Adds support for StorageEvent.


Patch:
diff --git a/packages/happy-dom/src/event/NonImplementedEventTypes.ts b/packages/happy-dom/src/event/NonImplementedEventTypes.ts
index 123d7351..ca606012 100644
--- a/packages/happy-dom/src/event/NonImplementedEventTypes.ts
+++ b/packages/happy-dom/src/event/NonImplementedEventTypes.ts
@@ -33,7 +33,6 @@ export default [
 	'RTCIdentityEvent',
 	'RTCPeerConnectionIceEvent',
 	'SensorEvent',
-	'StorageEvent',
 	'SVGEvent',
 	'SVGZoomEvent',
 	'TimeEvent',
diff --git a/packages/happy-dom/src/event/events/IStorageEventInit.ts b/packages/happy-dom/src/event/events/IStorageEventInit.ts
new file mode 100644
index 00000000..0fbc7d51
--- /dev/null
+++ b/packages/happy-dom/src/event/events/IStorageEventInit.ts
@@ -0,0 +1,9 @@
+import IEventInit from '../IEventInit';
+import Storage from '../../storage/Storage';
+
+export default interface IStorageEventInit extends IEventInit {
+	key?: string;
+	newValue?: string;
+	oldValue?: string;
+	storageArea?: Storage;
+}
diff --git a/packages/happy-dom/src/event/events/StorageEvent.ts b/packages/happy-dom/src/event/events/StorageEvent.ts
new file mode 100644
index 00000000..70460b80
--- /dev/null
+++ b/packages/happy-dom/src/event/events/StorageEvent.ts
@@ -0,0 +1,30 @@
+import Event from '../Event';
+import IStorageEventInit from './IStorageEventInit';
+import Storage from '../../storage/Storage';
+
+/**
+ *
+ */
+export default class StorageEvent extends Event {
+	public readonly key: string = null;
+	public readonly newValue: string = null;
+	public readonly oldValue: string = null;
+	public readonly storageArea: Storage = null;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param type Event type.
+	 * @param [eventInit] Event init.
+	 */
+	constructor(type: string, eventInit: IStorageEventInit = null) {
+		super(type);
+
+		if (eventInit) {
+			this.key = eventInit.key !== undefined ? eventInit.key : null;
+			this.newValue = eventInit.newValue !== undefined ? eventInit.newValue : null;
+			this.oldValue = eventInit.oldValue !== undefined ? eventInit.oldValue : null;
+			this.storageArea = eventInit.storageArea !== undefined ? eventInit.storageArea : null;
+		}
+	}
+}
diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts
index 3f6c66b1..ec8c72e4 100644
--- a/packages/happy-dom/src/nodes/document/Document.ts
+++ b/packages/happy-dom/src/nodes/document/Document.ts
@@ -689,4 +689,17 @@ export default class Document extends Node implements IDocument {
 		(<Document>adopted.ownerDocument) = this;
 		return adopted;
 	}
+
+	/**
+	 * @override
+	 */
+	public dispatchEvent(event: Event): boolean {
+		const returnValue = super.dispatchEvent(event);
+
+		if (event.bubbles && !event._propagationStopped) {
+			return this.defaultView.dispatchEvent(event);
+		}
+
+		return returnValue;
+	}
 }
diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts
index 25dd5a91..32aa5d44 100644
--- a/packages/happy-dom/src/window/IWindow.ts
+++ b/packages/happy-dom/src/window/IWindow.ts
@@ -49,6 +49,7 @@ import DataTransferItemList from '../event/DataTransferItemList';
 import InputEvent from '../event/events/InputEvent';
 import UIEvent from '../event/UIEvent';
 import ErrorEvent from '../event/events/ErrorEvent';
+import StorageEvent from '../event/events/StorageEvent';
 import Screen from '../screen/Screen';
 import AsyncTaskManager from './AsyncTaskManager';
 import IResponse from './IResponse';
@@ -111,6 +112,7 @@ export default interface IWindow {
 	readonly WheelEvent: typeof WheelEvent;
 	readonly InputEvent: typeof InputEvent;
 	readonly ErrorEvent: typeof ErrorEvent;
+	readonly StorageEvent: typeof StorageEvent;
 	readonly ProgressEvent: typeof ProgressEvent;
 	readonly EventTarget: typeof EventTarget;
 	readonly DataTransfer: typeof DataTransfer;
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index c326ebf1..4357ba8e 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -52,6 +52,7 @@ import DataTransferItemList from '../event/DataTransferItemList';
 import InputEvent from '../event/events/InputEvent';
 import UIEvent from '../event/UIEvent';
 import ErrorEvent from '../event/events/ErrorEvent';
+import StorageEvent from '../event/events/StorageEvent';
 import Screen from '../screen/Screen';
 import AsyncTaskManager from './AsyncTaskManager';
 import IResponse from './IResponse';
@@ -121,6 +122,7 @@ export default class Window extends EventTarget implements IWindow, NodeJS.Globa
 	public readonly WheelEvent = WheelEvent;
 	public readonly InputEvent = InputEvent;
 	public readonly ErrorEvent = ErrorEvent;
+	public readonly StorageEvent = StorageEvent;
 	public readonly ProgressEvent = ProgressEvent;
 	public readonly EventTarget = EventTarget;
 	public readonly DataTransfer = DataTransfer;


Test patch:
diff --git a/packages/happy-dom/test/nodes/document/Document.test.ts b/packages/happy-dom/test/nodes/document/Document.test.ts
index 34874009..da85a1b3 100644
--- a/packages/happy-dom/test/nodes/document/Document.test.ts
+++ b/packages/happy-dom/test/nodes/document/Document.test.ts
@@ -932,4 +932,16 @@ describe('Document', () => {
 			}, 0);
 		});
 	});
+
+	describe('dispatchEvent()', () => {
+		it('Bubbles events to Window.', () => {
+			const event = new Event('click', { bubbles: true });
+			let emittedEvent = null;
+
+			window.addEventListener('click', event => (emittedEvent = event));
+			document.dispatchEvent(event);
+
+			expect(emittedEvent).toBe(event);
+		});
+	});
 });