From e92a7aaab2c0f35a570e2329d5a24260ae75585f Mon Sep 17 00:00:00 2001 From: David Ortner Date: Tue, 10 May 2022 21:47:14 +0200 Subject: [PATCH] #468@minor: Adds support for Event.composedPath(). --- packages/happy-dom/src/event/Event.ts | 36 ++++++- packages/happy-dom/test/event/Event.test.ts | 103 ++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 packages/happy-dom/test/event/Event.test.ts diff --git a/packages/happy-dom/src/event/Event.ts b/packages/happy-dom/src/event/Event.ts index a4d377473..73ce1f614 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 = (this.target); + + while (eventTarget) { + composedPath.push(eventTarget); + + if (this.bubbles) { + if (this.composed && (eventTarget).host) { + eventTarget = (eventTarget).host; + } else if (((this.target)).ownerDocument === eventTarget) { + eventTarget = ((this.target)).ownerDocument.defaultView; + } else { + eventTarget = ((eventTarget)).parentNode || null; + } + } + } + + return composedPath; + } + /** * Init event. * 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 000000000..fa7ea261d --- /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 + ]); + }); + }); +});