Repository: capricorn86/happy-dom
Pull request #406 (2022-03-15 13:17:37)

#404@patch: Fixes issue with custom elements not being loaded when de…
…fined after the element tag was added to the DOM.


Patch:
diff --git a/.github/workflows/commit.yml b/.github/workflows/commit.yml
index c8f6deaf..c954dfde 100644
--- a/.github/workflows/commit.yml
+++ b/.github/workflows/commit.yml
@@ -1,13 +1,11 @@
 name: Commit
-on: [push]
+on: push
 jobs:
   build:
     runs-on: ubuntu-latest
     strategy:
       matrix:
         node-version: [16]
-    outputs:
-      sha_short: ${{ steps.vars.outputs.sha_short }}}
     steps:
       - uses: actions/checkout@v2
         with:
@@ -22,11 +20,7 @@ jobs:
           cache-name: cache-node-modules
         with:
           path: ~/.npm
-          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./packages/*/npm-shrinkwrap.json') }}-${{ hashFiles('./npm-shrinkwrap.json') }}
-          restore-keys: |
-            ${{ runner.os }}-build-${{ env.cache-name }}-
-            ${{ runner.os }}-build-
-            ${{ runner.os }}-
+          key: ${{ runner.os }}-${{ matrix.node-version }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('./package-lock.json') }}
       - run: npm ci --ignore-scripts
       - run: npm run bootstrap-ci
       - run: npm run validate-commit-messages
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7f21f7c0..b1dcb5c1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -21,7 +21,6 @@ jobs:
         uses: actions/setup-node@v1
         with:
           node-version: ${{ matrix.node-version }}
-      - run: npm ci --ignore-scripts
       - id: versionCheck
         run: |
           echo "::set-output name=next_version::$(npm run print-next-version --silent)"
@@ -42,6 +41,13 @@ jobs:
         uses: actions/setup-node@v1
         with:
           node-version: ${{ matrix.node-version }}
+      - name: Cache node modules
+        uses: actions/cache@v2
+        env:
+          cache-name: cache-node-modules
+        with:
+          path: ~/.npm
+          key: ${{ runner.os }}-${{ matrix.node-version }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('./package-lock.json') }}
       - run: npm ci --ignore-scripts
       - run: npm run bootstrap-ci
       - run: npm run compile
diff --git a/packages/happy-dom/src/config/ElementClass.ts b/packages/happy-dom/src/config/ElementClass.ts
deleted file mode 100644
index bf1a4536..00000000
--- a/packages/happy-dom/src/config/ElementClass.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import HTMLElement from '../nodes/html-element/HTMLElement';
-
-export default {
-	HTMLElement: HTMLElement,
-	HTMLHeadElement: HTMLElement,
-	HTMLTitleElement: HTMLElement,
-	HTMLBaseElement: HTMLElement,
-	HTMLMetaElement: HTMLElement,
-	HTMLBodyElement: HTMLElement,
-	HTMLHeadingElement: HTMLElement,
-	HTMLParagraphElement: HTMLElement,
-	HTMLHRElement: HTMLElement,
-	HTMLPreElement: HTMLElement,
-	HTMLUListElement: HTMLElement,
-	HTMLOListElement: HTMLElement,
-	HTMLLIElement: HTMLElement,
-	HTMLMenuElement: HTMLElement,
-	HTMLDListElement: HTMLElement,
-	HTMLDivElement: HTMLElement,
-	HTMLAnchorElement: HTMLElement,
-	HTMLAreaElement: HTMLElement,
-	HTMLBRElement: HTMLElement,
-	HTMLButtonElement: HTMLElement,
-	HTMLCanvasElement: HTMLElement,
-	HTMLDataElement: HTMLElement,
-	HTMLDataListElement: HTMLElement,
-	HTMLDetailsElement: HTMLElement,
-	HTMLDialogElement: HTMLElement,
-	HTMLDirectoryElement: HTMLElement,
-	HTMLFieldSetElement: HTMLElement,
-	HTMLFontElement: HTMLElement,
-	HTMLHtmlElement: HTMLElement,
-	HTMLLegendElement: HTMLElement,
-	HTMLMapElement: HTMLElement,
-	HTMLMarqueeElement: HTMLElement,
-	HTMLMediaElement: HTMLElement,
-	HTMLMeterElement: HTMLElement,
-	HTMLModElement: HTMLElement,
-	HTMLOptGroupElement: HTMLElement,
-	HTMLOptionElement: HTMLElement,
-	HTMLOutputElement: HTMLElement,
-	HTMLPictureElement: HTMLElement,
-	HTMLProgressElement: HTMLElement,
-	HTMLQuoteElement: HTMLElement,
-	HTMLSelectElement: HTMLElement,
-	HTMLSourceElement: HTMLElement,
-	HTMLSpanElement: HTMLElement,
-	HTMLTableCaptionElement: HTMLElement,
-	HTMLTableCellElement: HTMLElement,
-	HTMLTableColElement: HTMLElement,
-	HTMLTableElement: HTMLElement,
-	HTMLTimeElement: HTMLElement,
-	HTMLTableRowElement: HTMLElement,
-	HTMLTableSectionElement: HTMLElement,
-	HTMLUnknownElement: HTMLElement,
-	HTMLFrameElement: HTMLElement,
-	HTMLFrameSetElement: HTMLElement,
-	HTMLIFrameElement: HTMLElement,
-	HTMLEmbedElement: HTMLElement,
-	HTMLObjectElement: HTMLElement,
-	HTMLParamElement: HTMLElement,
-	HTMLVideoElement: HTMLElement,
-	HTMLAudioElement: HTMLElement,
-	HTMLTrackElement: HTMLElement
-};
diff --git a/packages/happy-dom/src/config/ElementTag.ts b/packages/happy-dom/src/config/ElementTag.ts
index 67255ca0..c9ce338c 100644
--- a/packages/happy-dom/src/config/ElementTag.ts
+++ b/packages/happy-dom/src/config/ElementTag.ts
@@ -1,3 +1,4 @@
+import HTMLElement from '../nodes/html-element/HTMLElement';
 import HTMLTemplateElement from '../nodes/html-template-element/HTMLTemplateElement';
 import HTMLFormElement from '../nodes/html-form-element/HTMLFormElement';
 import HTMLInputElement from '../nodes/html-input-element/HTMLInputElement';
@@ -13,25 +14,131 @@ import HTMLSlotElement from '../nodes/html-slot-element/HTMLSlotElement';
 import HTMLMetaElement from '../nodes/html-meta-element/HTMLMetaElement';
 
 export default {
-	template: HTMLTemplateElement,
-	form: HTMLFormElement,
-	input: HTMLInputElement,
-	textarea: HTMLTextAreaElement,
-	script: HTMLScriptElement,
-	img: HTMLImageElement,
-	link: HTMLLinkElement,
-	style: HTMLStyleElement,
-	label: HTMLLabelElement,
-	slot: HTMLSlotElement,
-	svg: SVGSVGElement,
-	circle: SVGElement,
-	ellipse: SVGElement,
-	line: SVGElement,
-	path: SVGElement,
-	polygon: SVGElement,
-	polyline: SVGElement,
-	rect: SVGElement,
-	stop: SVGElement,
-	use: SVGElement,
-	meta: HTMLMetaElement
+	A: HTMLElement,
+	ABBR: HTMLElement,
+	ADDRESS: HTMLElement,
+	AREA: HTMLElement,
+	ARTICLE: HTMLElement,
+	ASIDE: HTMLElement,
+	AUDIO: HTMLElement,
+	B: HTMLElement,
+	BASE: HTMLElement,
+	BDI: HTMLElement,
+	BDO: HTMLElement,
+	BLOCKQUAOTE: HTMLElement,
+	BODY: HTMLElement,
+	TEMPLATE: HTMLTemplateElement,
+	FORM: HTMLFormElement,
+	INPUT: HTMLInputElement,
+	TEXTAREA: HTMLTextAreaElement,
+	SCRIPT: HTMLScriptElement,
+	IMG: HTMLImageElement,
+	LINK: HTMLLinkElement,
+	STYLE: HTMLStyleElement,
+	LABEL: HTMLLabelElement,
+	SLOT: HTMLSlotElement,
+	SVG: SVGSVGElement,
+	CIRCLE: SVGElement,
+	ELLIPSE: SVGElement,
+	LINE: SVGElement,
+	PATH: SVGElement,
+	POLYGON: SVGElement,
+	POLYLINE: SVGElement,
+	RECT: SVGElement,
+	STOP: SVGElement,
+	USE: SVGElement,
+	META: HTMLMetaElement,
+	BLOCKQUOTE: HTMLElement,
+	BR: HTMLElement,
+	BUTTON: HTMLElement,
+	CANVAS: HTMLElement,
+	CAPTION: HTMLElement,
+	CITE: HTMLElement,
+	CODE: HTMLElement,
+	COL: HTMLElement,
+	COLGROUP: HTMLElement,
+	DATA: HTMLElement,
+	DATALIST: HTMLElement,
+	DD: HTMLElement,
+	DEL: HTMLElement,
+	DETAILS: HTMLElement,
+	DFN: HTMLElement,
+	DIALOG: HTMLElement,
+	DIV: HTMLElement,
+	DL: HTMLElement,
+	DT: HTMLElement,
+	EM: HTMLElement,
+	EMBED: HTMLElement,
+	FIELDSET: HTMLElement,
+	FIGCAPTION: HTMLElement,
+	FIGURE: HTMLElement,
+	FOOTER: HTMLElement,
+	H1: HTMLElement,
+	H2: HTMLElement,
+	H3: HTMLElement,
+	H4: HTMLElement,
+	H5: HTMLElement,
+	H6: HTMLElement,
+	HEAD: HTMLElement,
+	HEADER: HTMLElement,
+	HGROUP: HTMLElement,
+	HR: HTMLElement,
+	HTML: HTMLElement,
+	I: HTMLElement,
+	IFRAME: HTMLElement,
+	INS: HTMLElement,
+	KBD: HTMLElement,
+	LEGEND: HTMLElement,
+	LI: HTMLElement,
+	MAIN: HTMLElement,
+	MAP: HTMLElement,
+	MARK: HTMLElement,
+	MATH: HTMLElement,
+	MENU: HTMLElement,
+	MENUITEM: HTMLElement,
+	METER: HTMLElement,
+	NAV: HTMLElement,
+	NOSCRIPT: HTMLElement,
+	OBJECT: HTMLElement,
+	OL: HTMLElement,
+	OPTGROUP: HTMLElement,
+	OPTION: HTMLElement,
+	OUTPUT: HTMLElement,
+	P: HTMLElement,
+	PARAM: HTMLElement,
+	PICTURE: HTMLElement,
+	PRE: HTMLElement,
+	PROGRESS: HTMLElement,
+	Q: HTMLElement,
+	RB: HTMLElement,
+	RP: HTMLElement,
+	RT: HTMLElement,
+	RTC: HTMLElement,
+	RUBY: HTMLElement,
+	S: HTMLElement,
+	SAMP: HTMLElement,
+	SECTION: HTMLElement,
+	SELECT: HTMLElement,
+	SMALL: HTMLElement,
+	SOURCE: HTMLElement,
+	SPAN: HTMLElement,
+	STRONG: HTMLElement,
+	SUB: HTMLElement,
+	SUMMARY: HTMLElement,
+	SUP: HTMLElement,
+	TABLE: HTMLElement,
+	TBODY: HTMLElement,
+	TD: HTMLElement,
+	TFOOT: HTMLElement,
+	TH: HTMLElement,
+	THEAD: HTMLElement,
+	TIME: HTMLElement,
+	TITLE: HTMLElement,
+	TR: HTMLElement,
+	TRACK: HTMLElement,
+	U: HTMLElement,
+	UL: HTMLElement,
+	VAR: HTMLElement,
+	VIDEO: HTMLElement,
+	WBR: HTMLElement
 };
diff --git a/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts b/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts
new file mode 100644
index 00000000..2542b897
--- /dev/null
+++ b/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts
@@ -0,0 +1,61 @@
+export default [
+	'HTMLHeadElement',
+	'HTMLTitleElement',
+	'HTMLBaseElement',
+	'HTMLMetaElement',
+	'HTMLBodyElement',
+	'HTMLHeadingElement',
+	'HTMLParagraphElement',
+	'HTMLHRElement',
+	'HTMLPreElement',
+	'HTMLUListElement',
+	'HTMLOListElement',
+	'HTMLLIElement',
+	'HTMLMenuElement',
+	'HTMLDListElement',
+	'HTMLDivElement',
+	'HTMLAnchorElement',
+	'HTMLAreaElement',
+	'HTMLBRElement',
+	'HTMLButtonElement',
+	'HTMLCanvasElement',
+	'HTMLDataElement',
+	'HTMLDataListElement',
+	'HTMLDetailsElement',
+	'HTMLDialogElement',
+	'HTMLDirectoryElement',
+	'HTMLFieldSetElement',
+	'HTMLFontElement',
+	'HTMLHtmlElement',
+	'HTMLLegendElement',
+	'HTMLMapElement',
+	'HTMLMarqueeElement',
+	'HTMLMediaElement',
+	'HTMLMeterElement',
+	'HTMLModElement',
+	'HTMLOptGroupElement',
+	'HTMLOptionElement',
+	'HTMLOutputElement',
+	'HTMLPictureElement',
+	'HTMLProgressElement',
+	'HTMLQuoteElement',
+	'HTMLSelectElement',
+	'HTMLSourceElement',
+	'HTMLSpanElement',
+	'HTMLTableCaptionElement',
+	'HTMLTableCellElement',
+	'HTMLTableColElement',
+	'HTMLTableElement',
+	'HTMLTimeElement',
+	'HTMLTableRowElement',
+	'HTMLTableSectionElement',
+	'HTMLFrameElement',
+	'HTMLFrameSetElement',
+	'HTMLIFrameElement',
+	'HTMLEmbedElement',
+	'HTMLObjectElement',
+	'HTMLParamElement',
+	'HTMLVideoElement',
+	'HTMLAudioElement',
+	'HTMLTrackElement'
+];
diff --git a/packages/happy-dom/src/custom-element/CustomElementRegistry.ts b/packages/happy-dom/src/custom-element/CustomElementRegistry.ts
index 53184e96..acc9a305 100644
--- a/packages/happy-dom/src/custom-element/CustomElementRegistry.ts
+++ b/packages/happy-dom/src/custom-element/CustomElementRegistry.ts
@@ -7,7 +7,7 @@ import Node from '../nodes/node/Node';
  */
 export default class CustomElementRegistry {
 	public _registry: { [k: string]: { elementClass: typeof HTMLElement; extends: string } } = {};
-	private _callbacks: { [k: string]: (() => void)[] } = {};
+	public _callbacks: { [k: string]: (() => void)[] } = {};
 
 	/**
 	 * Defines a custom element class.
@@ -22,16 +22,17 @@ export default class CustomElementRegistry {
 		elementClass: typeof HTMLElement,
 		options?: { extends: string }
 	): void {
-		const name = tagName.toLowerCase();
-		if (!name.includes('-')) {
+		const upperTagName = tagName.toUpperCase();
+
+		if (!upperTagName.includes('-')) {
 			throw new DOMException(
 				"Failed to execute 'define' on 'CustomElementRegistry': \"" +
-					name +
+					tagName +
 					'" is not a valid custom element name.'
 			);
 		}
 
-		this._registry[name] = {
+		this._registry[upperTagName] = {
 			elementClass,
 			extends: options && options.extends ? options.extends.toLowerCase() : null
 		};
@@ -41,11 +42,12 @@ export default class CustomElementRegistry {
 			elementClass._observedAttributes = elementClass.observedAttributes;
 		}
 
-		if (this._callbacks[name]) {
-			for (const callback of this._callbacks[name]) {
+		if (this._callbacks[upperTagName]) {
+			const callbacks = this._callbacks[upperTagName];
+			delete this._callbacks[upperTagName];
+			for (const callback of callbacks) {
 				callback();
 			}
-			delete this._callbacks[name];
 		}
 	}
 
@@ -56,8 +58,8 @@ export default class CustomElementRegistry {
 	 * @param HTMLElement Class defined.
 	 */
 	public get(tagName: string): typeof HTMLElement {
-		const name = tagName.toLowerCase();
-		return this._registry[name] ? this._registry[name].elementClass : undefined;
+		const upperTagName = tagName.toUpperCase();
+		return this._registry[upperTagName] ? this._registry[upperTagName].elementClass : undefined;
 	}
 
 	/**
@@ -78,13 +80,13 @@ export default class CustomElementRegistry {
 	 * @returns Promise.
 	 */
 	public whenDefined(tagName: string): Promise<void> {
-		const name = tagName.toLowerCase();
-		if (this.get(name)) {
+		const upperTagName = tagName.toUpperCase();
+		if (this.get(upperTagName)) {
 			return Promise.resolve();
 		}
 		return new Promise(resolve => {
-			this._callbacks[name] = this._callbacks[name] || [];
-			this._callbacks[name].push(resolve);
+			this._callbacks[upperTagName] = this._callbacks[upperTagName] || [];
+			this._callbacks[upperTagName].push(resolve);
 		});
 	}
 }
diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts
index c82fa83a..b16d5217 100644
--- a/packages/happy-dom/src/index.ts
+++ b/packages/happy-dom/src/index.ts
@@ -99,10 +99,6 @@ import TreeWalker from './tree-walker/TreeWalker';
 import CustomElementRegistry from './custom-element/CustomElementRegistry';
 import XMLParser from './xml-parser/XMLParser';
 import XMLSerializer from './xml-serializer/XMLSerializer';
-import ElementTag from './config/ElementTag';
-import SelfClosingElements from './config/SelfClosingElements';
-import UnclosedElements from './config/UnclosedElements';
-import ChildLessElements from './config/ChildLessElements';
 import CSSStyleSheet from './css/CSSStyleSheet';
 import Storage from './storage/Storage';
 import URLSearchParams from './url-search-params/URLSearchParams';
@@ -210,10 +206,6 @@ export {
 	CustomElementRegistry,
 	XMLParser,
 	XMLSerializer,
-	ElementTag,
-	SelfClosingElements,
-	UnclosedElements,
-	ChildLessElements,
 	CSSStyleSheet,
 	Storage,
 	URLSearchParams,
diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts
index a857c1e7..a92f0413 100644
--- a/packages/happy-dom/src/nodes/document/Document.ts
+++ b/packages/happy-dom/src/nodes/document/Document.ts
@@ -1,5 +1,5 @@
 import Element from '../element/Element';
-import HTMLElement from '../html-element/HTMLElement';
+import HTMLUnknownElement from '../html-unknown-element/HTMLUnknownElement';
 import Text from '../text/Text';
 import Comment from '../comment/Comment';
 import Window from '../../window/Window';
@@ -572,21 +572,21 @@ export default class Document extends Node implements IDocument {
 		qualifiedName: string,
 		options?: { is: string }
 	): IElement {
+		const tagName = qualifiedName.toUpperCase();
+
 		let customElementClass;
 		if (this.defaultView && options && options.is) {
 			customElementClass = this.defaultView.customElements.get(options.is);
 		} else if (this.defaultView) {
-			customElementClass = this.defaultView.customElements.get(qualifiedName);
+			customElementClass = this.defaultView.customElements.get(tagName);
 		}
 
-		const elementClass = customElementClass
-			? customElementClass
-			: ElementTag[qualifiedName] || HTMLElement;
+		const elementClass = customElementClass || ElementTag[tagName] || HTMLUnknownElement;
 
 		elementClass.ownerDocument = this;
 
 		const element = new elementClass();
-		element.tagName = qualifiedName.toUpperCase();
+		element.tagName = tagName;
 		element.ownerDocument = this;
 		element.namespaceURI = namespaceURI;
 
diff --git a/packages/happy-dom/src/nodes/html-unknown-element/HTMLUnknownElement.ts b/packages/happy-dom/src/nodes/html-unknown-element/HTMLUnknownElement.ts
new file mode 100644
index 00000000..2aadaa03
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-unknown-element/HTMLUnknownElement.ts
@@ -0,0 +1,52 @@
+import HTMLElement from '../html-element/HTMLElement';
+import INode from '../node/INode';
+import IHTMLElement from '../html-element/IHTMLElement';
+
+/**
+ * HTML Unknown Element.
+ *
+ * Reference:
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLUnknownElement.
+ */
+export default class HTMLUnknownElement extends HTMLElement implements IHTMLElement {
+	private _customElementDefineCallback: () => void = null;
+
+	/**
+	 * Connects this element to another element.
+	 *
+	 * @param parentNode Parent node.
+	 */
+	public _connectToNode(parentNode: INode = null): void {
+		const tagName = this.tagName;
+
+		// This element can potentially be a custom element that has not been defined yet
+		// Therefore we need to register a callback for when it is defined in CustomElementRegistry and replace it with the registered element (see #404)
+		if (tagName.includes('-')) {
+			const callbacks = this.ownerDocument.defaultView.customElements._callbacks;
+
+			if (parentNode && !this._customElementDefineCallback) {
+				const callback = (): void => {
+					if (this.parentNode) {
+						const newElement = this.ownerDocument.createElement(tagName);
+						this.parentNode.insertBefore(newElement, this);
+						this.parentNode.removeChild(this);
+					}
+				};
+				callbacks[tagName] = callbacks[tagName] || [];
+				callbacks[tagName].push(callback);
+				this._customElementDefineCallback = callback;
+			} else if (!parentNode && callbacks[tagName] && this._customElementDefineCallback) {
+				const index = callbacks[tagName].indexOf(this._customElementDefineCallback);
+				if (index !== -1) {
+					callbacks[tagName].splice(index, 1);
+				}
+				if (!callbacks[tagName].length) {
+					delete callbacks[tagName];
+				}
+				this._customElementDefineCallback = null;
+			}
+		}
+
+		super._connectToNode(parentNode);
+	}
+}
diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts
index 89a63f64..bfee56c1 100644
--- a/packages/happy-dom/src/window/IWindow.ts
+++ b/packages/happy-dom/src/window/IWindow.ts
@@ -11,6 +11,7 @@ import Element from '../nodes/element/Element';
 import HTMLTemplateElement from '../nodes/html-template-element/HTMLTemplateElement';
 import HTMLFormElement from '../nodes/html-form-element/HTMLFormElement';
 import HTMLElement from '../nodes/html-element/HTMLElement';
+import HTMLUnknownElement from '../nodes/html-unknown-element/HTMLUnknownElement';
 import HTMLInputElement from '../nodes/html-input-element/HTMLInputElement';
 import HTMLTextAreaElement from '../nodes/html-text-area-element/HTMLTextAreaElement';
 import HTMLLinkElement from '../nodes/html-link-element/HTMLLinkElement';
@@ -90,6 +91,7 @@ export default interface IWindow extends IEventTarget {
 	// Global classes
 	readonly Node: typeof Node;
 	readonly HTMLElement: typeof HTMLElement;
+	readonly HTMLUnknownElement: typeof HTMLUnknownElement;
 	readonly HTMLTemplateElement: typeof HTMLTemplateElement;
 	readonly HTMLFormElement: typeof HTMLFormElement;
 	readonly HTMLInputElement: typeof HTMLInputElement;
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index 59b96cf3..d4d00eac 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -12,6 +12,7 @@ import Element from '../nodes/element/Element';
 import HTMLTemplateElement from '../nodes/html-template-element/HTMLTemplateElement';
 import HTMLFormElement from '../nodes/html-form-element/HTMLFormElement';
 import HTMLElement from '../nodes/html-element/HTMLElement';
+import HTMLUnknownElement from '../nodes/html-unknown-element/HTMLUnknownElement';
 import HTMLInputElement from '../nodes/html-input-element/HTMLInputElement';
 import HTMLTextAreaElement from '../nodes/html-text-area-element/HTMLTextAreaElement';
 import HTMLLinkElement from '../nodes/html-link-element/HTMLLinkElement';
@@ -37,7 +38,7 @@ import URL from '../location/URL';
 import Location from '../location/Location';
 import NonImplementedEventTypes from '../event/NonImplementedEventTypes';
 import MutationObserver from '../mutation-observer/MutationObserver';
-import ElementClass from '../config/ElementClass';
+import NonImplemenetedElementClasses from '../config/NonImplemenetedElementClasses';
 import DOMParser from '../dom-parser/DOMParser';
 import XMLSerializer from '../xml-serializer/XMLSerializer';
 import ResizeObserver from '../resize-observer/ResizeObserver';
@@ -101,6 +102,7 @@ export default class Window extends EventTarget implements IWindow, NodeJS.Globa
 	// Global classes
 	public readonly Node = Node;
 	public readonly HTMLElement = HTMLElement;
+	public readonly HTMLUnknownElement = HTMLUnknownElement;
 	public readonly HTMLTemplateElement = HTMLTemplateElement;
 	public readonly HTMLFormElement = HTMLFormElement;
 	public readonly HTMLInputElement = HTMLInputElement;
@@ -282,9 +284,9 @@ export default class Window extends EventTarget implements IWindow, NodeJS.Globa
 			}
 		}
 
-		for (const className of Object.keys(ElementClass)) {
+		for (const className of NonImplemenetedElementClasses) {
 			if (!this[className]) {
-				this[className] = ElementClass[className];
+				this[className] = HTMLElement;
 			}
 		}
 
diff --git a/packages/server-rendering/src/renderer/HappyDOMServerRenderer.ts b/packages/server-rendering/src/renderer/HappyDOMServerRenderer.ts
index 16c843c8..fae90f98 100644
--- a/packages/server-rendering/src/renderer/HappyDOMServerRenderer.ts
+++ b/packages/server-rendering/src/renderer/HappyDOMServerRenderer.ts
@@ -1,14 +1,6 @@
-import {
-	Node,
-	INode,
-	HTMLElement,
-	Element,
-	IElement,
-	DocumentType,
-	IShadowRoot,
-	SelfClosingElements,
-	UnclosedElements
-} from 'happy-dom';
+import { Node, INode, HTMLElement, Element, IElement, DocumentType, IShadowRoot } from 'happy-dom';
+import SelfClosingElements from 'happy-dom/lib/config/SelfClosingElements';
+import UnclosedElements from 'happy-dom/lib/config/UnclosedElements';
 import ShadowRootRenderer from './shadow-root/ShadowRootRenderer';
 import IHappyDOMServerRenderOptions from './IHappyDOMServerRenderOptions';
 import HappyDOMServerRenderResult from './HappyDOMServerRenderResult';


Test patch:
diff --git a/packages/happy-dom/test/custom-element/CustomElementRegistry.test.ts b/packages/happy-dom/test/custom-element/CustomElementRegistry.test.ts
index c347d432..d389fb4c 100644
--- a/packages/happy-dom/test/custom-element/CustomElementRegistry.test.ts
+++ b/packages/happy-dom/test/custom-element/CustomElementRegistry.test.ts
@@ -20,7 +20,7 @@ describe('CustomElementRegistry', () => {
 				extends: 'ul'
 			});
 			expect(customElements.get('custom-element')).toBe(CustomElement);
-			expect(customElements._registry['custom-element'].extends).toBe('ul');
+			expect(customElements._registry['CUSTOM-ELEMENT'].extends).toBe('ul');
 		});
 
 		it('Throws an error if tag name does not contain "-".', () => {
diff --git a/packages/happy-dom/test/nodes/html-unknown-element/HTMLUnknownElement.test.ts b/packages/happy-dom/test/nodes/html-unknown-element/HTMLUnknownElement.test.ts
new file mode 100644
index 00000000..1842b7bb
--- /dev/null
+++ b/packages/happy-dom/test/nodes/html-unknown-element/HTMLUnknownElement.test.ts
@@ -0,0 +1,43 @@
+import Window from '../../../src/window/Window';
+import IWindow from '../../../src/window/IWindow';
+import IDocument from '../../../src/nodes/document/IDocument';
+import HTMLUnknownElement from '../../../src/nodes/html-unknown-element/HTMLUnknownElement';
+import CustomElement from '../../CustomElement';
+
+describe('HTMLUnknownElement', () => {
+	let window: IWindow;
+	let document: IDocument;
+
+	beforeEach(() => {
+		window = new Window();
+		document = window.document;
+	});
+
+	describe('_connectNode()', () => {
+		it('Waits for a custom element to be defined and replace it when it is.', () => {
+			const element = <HTMLUnknownElement>document.createElement('custom-element');
+			const parent = document.createElement('div');
+
+			parent.appendChild(element);
+
+			expect(window.customElements._callbacks['CUSTOM-ELEMENT'].length).toBe(1);
+
+			parent.removeChild(element);
+
+			expect(Object.keys(window.customElements._callbacks).length).toBe(0);
+
+			parent.appendChild(element);
+
+			window.customElements.define('custom-element', CustomElement);
+
+			expect(parent.children.length).toBe(1);
+
+			expect(parent.children[0] !== element).toBe(true);
+			expect(parent.children[0].shadowRoot.children.length).toBe(0);
+
+			document.body.appendChild(parent);
+
+			expect(parent.children[0].shadowRoot.children.length).toBe(2);
+		});
+	});
+});