diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts index f404d5ce7..922818a55 100644 --- a/packages/happy-dom/src/nodes/document/Document.ts +++ b/packages/happy-dom/src/nodes/document/Document.ts @@ -37,6 +37,7 @@ import DocumentReadyStateEnum from './DocumentReadyStateEnum'; import DocumentReadyStateManager from './DocumentReadyStateManager'; import Location from '../../location/Location'; import Selection from '../../selection/Selection'; +import IShadowRoot from '../shadow-root/IShadowRoot'; /** * Document. @@ -240,6 +241,17 @@ export default class Document extends Node implements IDocument { * @returns Active element. */ public get activeElement(): IHTMLElement { + if (this._activeElement) { + let rootNode: IShadowRoot | IDocument = ( + this._activeElement.getRootNode() + ); + let activeElement: IHTMLElement = this._activeElement; + while (rootNode !== this) { + activeElement = (rootNode).host; + rootNode = activeElement.getRootNode(); + } + return activeElement; + } return this._activeElement || this.body || this.documentElement || null; } diff --git a/packages/happy-dom/test/nodes/document/Document.test.ts b/packages/happy-dom/test/nodes/document/Document.test.ts index 5cc68363f..e513d3035 100644 --- a/packages/happy-dom/test/nodes/document/Document.test.ts +++ b/packages/happy-dom/test/nodes/document/Document.test.ts @@ -30,6 +30,8 @@ import ISVGElement from '../../../src/nodes/svg-element/ISVGElement'; import CustomEvent from '../../../src/event/events/CustomEvent'; import Selection from '../../../src/selection/Selection'; +/* eslint-disable jsdoc/require-jsdoc */ + describe('Document', () => { let window: IWindow; let document: IDocument; @@ -309,6 +311,60 @@ describe('Document', () => { expect(document.activeElement === document.body).toBe(true); }); + + it('Returns the first custom element that has document as root node when the focused element is nestled in multiple shadow roots.', () => { + class CustomElementA extends window.HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + + public connectedCallback(): void { + this.shadowRoot.innerHTML = ` +
+ +
+ `; + } + } + class CustomElementB extends window.HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + + public connectedCallback(): void { + this.shadowRoot.innerHTML = ` +
+ +
+ `; + } + } + + window.customElements.define('custom-element-a', CustomElementA); + window.customElements.define('custom-element-b', CustomElementB); + + const customElementA = document.createElement('custom-element-a'); + const div = document.createElement('div'); + div.appendChild(customElementA); + document.body.appendChild(div); + + const button = ( + (( + customElementA.shadowRoot.querySelector('custom-element-b') + )).shadowRoot.querySelector('button') + ); + + let focusCalls = 0; + button.addEventListener('focus', () => focusCalls++); + + button.focus(); + button.focus(); + + expect(document.activeElement).toBe(customElementA); + expect(focusCalls).toBe(1); + }); }); describe('get scrollingElement()', () => { diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index 3b5a61f56..16cb02d6b 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -100,7 +100,7 @@ describe('Window', () => { describe('get Array()', () => { it('Is the same as [].constructor.', () => { - expect((() => {}).constructor).toBe(window.Function); + expect([].constructor).toBe(window.Array); const context = VM.createContext(new Window()); expect(context.eval('[].constructor === window.Array')).toBe(true);