Repository: capricorn86/happy-dom
Pull request #436 (2022-03-30 13:00:26)

#434@patch: Fixes issue with Document.activeElement not being the fir…
…st custom element that has document as root node when the focused element is nestled in shadow roots.


Patch:
diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts
index f404d5ce..922818a5 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 = <IShadowRoot | IDocument>(
+				this._activeElement.getRootNode()
+			);
+			let activeElement: IHTMLElement = this._activeElement;
+			while (rootNode !== this) {
+				activeElement = <IHTMLElement>(<IShadowRoot>rootNode).host;
+				rootNode = <IShadowRoot | IDocument>activeElement.getRootNode();
+			}
+			return activeElement;
+		}
 		return this._activeElement || this.body || this.documentElement || null;
 	}
 


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 5cc68363..e513d303 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 = `
+						<div>
+							<custom-element-b></custom-element-b>
+						</div>
+					`;
+				}
+			}
+			class CustomElementB extends window.HTMLElement {
+				constructor() {
+					super();
+					this.attachShadow({ mode: 'open' });
+				}
+
+				public connectedCallback(): void {
+					this.shadowRoot.innerHTML = `
+						<div>
+							<button tabindex="0"></button>
+						</div>
+					`;
+				}
+			}
+
+			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 = <IHTMLElement>(
+				(<IHTMLElement>(
+					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 3b5a61f5..16cb02d6 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);