Repository: capricorn86/happy-dom
Pull request #473 (2022-05-10 19:52:03)

#468@minor: Adds support for Event.composedPath().


Patch:
diff --git a/packages/happy-dom/src/event/Event.ts b/packages/happy-dom/src/event/Event.ts
index a4d37747..73ce1f61 100644
--- a/packages/happy-dom/src/event/Event.ts
+++ b/packages/happy-dom/src/event/Event.ts
@@ -1,8 +1,12 @@
 import IEventInit from './IEventInit';
 import EventTarget from './EventTarget';
+import INode from '../nodes/node/INode';
+import IWindow from '../window/IWindow';
+import IShadowRoot from '../nodes/shadow-root/IShadowRoot';
+import IEventTarget from './IEventTarget';
 
 /**
- *
+ * Event.
  */
 export default class Event {
 	public composed = false;
@@ -31,6 +35,36 @@ export default class Event {
 		}
 	}
 
+	/**
+	 * Returns composed path.
+	 *
+	 * @returns Composed path.
+	 */
+	public composedPath(): IEventTarget[] {
+		if (!this.target) {
+			return [];
+		}
+
+		const composedPath = [];
+		let eventTarget: INode | IShadowRoot | IWindow = <INode | IShadowRoot>(<unknown>this.target);
+
+		while (eventTarget) {
+			composedPath.push(eventTarget);
+
+			if (this.bubbles) {
+				if (this.composed && (<IShadowRoot>eventTarget).host) {
+					eventTarget = (<IShadowRoot>eventTarget).host;
+				} else if ((<INode>(<unknown>this.target)).ownerDocument === eventTarget) {
+					eventTarget = (<INode>(<unknown>this.target)).ownerDocument.defaultView;
+				} else {
+					eventTarget = (<INode>(<unknown>eventTarget)).parentNode || null;
+				}
+			}
+		}
+
+		return composedPath;
+	}
+
 	/**
 	 * Init event.
 	 *


Test patch:
diff --git a/packages/happy-dom/test/event/Event.test.ts b/packages/happy-dom/test/event/Event.test.ts
new file mode 100644
index 00000000..fa7ea261
--- /dev/null
+++ b/packages/happy-dom/test/event/Event.test.ts
@@ -0,0 +1,103 @@
+import IWindow from '../../src/window/IWindow';
+import Window from '../../src/window/Window';
+import IDocument from '../../src/nodes/document/IDocument';
+import Event from '../../src/event/Event';
+import CustomElement from '../CustomElement';
+
+describe('Event', () => {
+	let window: IWindow;
+	let document: IDocument;
+
+	beforeEach(() => {
+		window = new Window();
+		document = window.document;
+
+		window.customElements.define('custom-element', CustomElement);
+	});
+
+	describe('composedPath()', () => {
+		it('Returns a composed path.', () => {
+			const div = document.createElement('div');
+			const span = document.createElement('span');
+			let composedPath = null;
+
+			div.appendChild(span);
+			document.body.appendChild(div);
+
+			div.addEventListener('click', (event: Event) => {
+				composedPath = event.composedPath();
+			});
+
+			span.dispatchEvent(
+				new Event('click', {
+					bubbles: true
+				})
+			);
+
+			expect(composedPath).toEqual([
+				span,
+				div,
+				document.body,
+				document.documentElement,
+				document,
+				window
+			]);
+		});
+
+		it('Goes through shadow roots if composed is set to "true".', () => {
+			const div = document.createElement('div');
+			const customELement = document.createElement('custom-element');
+			let composedPath = null;
+
+			div.appendChild(customELement);
+
+			document.body.appendChild(div);
+
+			div.addEventListener('click', (event: Event) => {
+				composedPath = event.composedPath();
+			});
+
+			customELement.shadowRoot.children[1].children[0].dispatchEvent(
+				new Event('click', {
+					bubbles: true,
+					composed: true
+				})
+			);
+
+			expect(composedPath).toEqual([
+				customELement.shadowRoot.children[1].children[0],
+				customELement.shadowRoot.children[1],
+				customELement.shadowRoot,
+				customELement,
+				div,
+				document.body,
+				document.documentElement,
+				document,
+				window
+			]);
+		});
+
+		it('Does not go through shadow roots if composed is set to "false".', () => {
+			const customELement = document.createElement('custom-element');
+			let composedPath = null;
+
+			document.body.appendChild(customELement);
+
+			customELement.shadowRoot.children[1].addEventListener('click', (event: Event) => {
+				composedPath = event.composedPath();
+			});
+
+			customELement.shadowRoot.children[1].children[0].dispatchEvent(
+				new Event('click', {
+					bubbles: true
+				})
+			);
+
+			expect(composedPath).toEqual([
+				customELement.shadowRoot.children[1].children[0],
+				customELement.shadowRoot.children[1],
+				customELement.shadowRoot
+			]);
+		});
+	});
+});