Repository: capricorn86/happy-dom
Pull request #837 (2023-04-04 15:47:32)

#834@patch: Fixes event handling and parsing of template element.


Patch:
diff --git a/packages/happy-dom/src/config/ChildLessElements.ts b/packages/happy-dom/src/config/ChildLessElements.ts
index e259d4a0..dc5606eb 100644
--- a/packages/happy-dom/src/config/ChildLessElements.ts
+++ b/packages/happy-dom/src/config/ChildLessElements.ts
@@ -1,1 +1,1 @@
-export default ['style', 'script', 'template'];
+export default ['style', 'script'];
diff --git a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts
index 6e7b9e47..2be3afc7 100644
--- a/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts
+++ b/packages/happy-dom/src/css/declaration/utilities/CSSStyleDeclarationPropertyManager.ts
@@ -339,7 +339,7 @@ export default class CSSStyleDeclarationPropertyManager {
 					important
 				);
 				break;
-			case 'border-bottom-right-radius':
+			case 'border-bottom-left-radius':
 				properties = CSSStyleDeclarationPropertySetParser.getBorderBottomLeftRadius(
 					value,
 					important
diff --git a/packages/happy-dom/src/event/Event.ts b/packages/happy-dom/src/event/Event.ts
index 541d5413..4ae8aa22 100644
--- a/packages/happy-dom/src/event/Event.ts
+++ b/packages/happy-dom/src/event/Event.ts
@@ -20,6 +20,7 @@ export default class Event {
 	public _currentTarget: IEventTarget = null;
 	public timeStamp: number = performance.now();
 	public type: string = null;
+	public _isInPassiveEventListener = false;
 
 	/**
 	 * Constructor.
@@ -55,6 +56,15 @@ export default class Event {
 		return this._currentTarget;
 	}
 
+	/**
+	 * Returns "true" if propagation has been stopped.
+	 *
+	 * @returns "true" if propagation has been stopped.
+	 */
+	public get cancelBubble(): boolean {
+		return this._propagationStopped;
+	}
+
 	/**
 	 * Returns composed path.
 	 *
@@ -107,7 +117,9 @@ export default class Event {
 	 * Prevents default.
 	 */
 	public preventDefault(): void {
-		this.defaultPrevented = true;
+		if (!this._isInPassiveEventListener) {
+			this.defaultPrevented = true;
+		}
 	}
 
 	/**
diff --git a/packages/happy-dom/src/event/EventTarget.ts b/packages/happy-dom/src/event/EventTarget.ts
index 6396f4b7..0d661a71 100644
--- a/packages/happy-dom/src/event/EventTarget.ts
+++ b/packages/happy-dom/src/event/EventTarget.ts
@@ -81,12 +81,18 @@ export default abstract class EventTarget implements IEventTarget {
 				const listener = listeners[i];
 				const options = listenerOptions[i];
 
+				if (options?.passive) {
+					event._isInPassiveEventListener = true;
+				}
+
 				if ((<IEventListener>listener).handleEvent) {
 					(<IEventListener>listener).handleEvent(event);
 				} else {
 					(<(event: Event) => void>listener).call(this, event);
 				}
 
+				event._isInPassiveEventListener = false;
+
 				if (options?.once) {
 					this.removeEventListener(event.type, listener);
 				}
diff --git a/packages/happy-dom/src/event/IEventTarget.ts b/packages/happy-dom/src/event/IEventTarget.ts
index d5784fbe..d9e51e5f 100644
--- a/packages/happy-dom/src/event/IEventTarget.ts
+++ b/packages/happy-dom/src/event/IEventTarget.ts
@@ -1,5 +1,6 @@
 import IEventListener from './IEventListener';
 import Event from './Event';
+import IEventListenerOptions from './IEventListenerOptions';
 
 /**
  * Handles events.
@@ -11,7 +12,11 @@ export default interface IEventTarget {
 	 * @param type Event type.
 	 * @param listener Listener.
 	 */
-	addEventListener(type: string, listener: ((event: Event) => void) | IEventListener): void;
+	addEventListener(
+		type: string,
+		listener: ((event: Event) => void) | IEventListener,
+		options?: IEventListenerOptions
+	): void;
 
 	/**
 	 * Adds an event listener.
diff --git a/packages/happy-dom/src/event/UIEvent.ts b/packages/happy-dom/src/event/UIEvent.ts
index 1e5c28ce..1f5a17f0 100644
--- a/packages/happy-dom/src/event/UIEvent.ts
+++ b/packages/happy-dom/src/event/UIEvent.ts
@@ -31,39 +31,4 @@ export default class UIEvent extends Event {
 			this.view = eventInit.view || null;
 		}
 	}
-
-	/**
-	 * Init event.
-	 *
-	 * @deprecated
-	 * @param type Type.
-	 * @param [bubbles=false] "true" if it bubbles.
-	 * @param [cancelable=false] "true" if it cancelable.
-	 */
-	public initEvent(type: string, bubbles = false, cancelable = false): void {
-		this.type = type;
-		this.bubbles = bubbles;
-		this.cancelable = cancelable;
-	}
-
-	/**
-	 * Prevents default.
-	 */
-	public preventDefault(): void {
-		this.defaultPrevented = true;
-	}
-
-	/**
-	 * Stops immediate propagation.
-	 */
-	public stopImmediatePropagation(): void {
-		this._immediatePropagationStopped = true;
-	}
-
-	/**
-	 * Stops propagation.
-	 */
-	public stopPropagation(): void {
-		this._propagationStopped = true;
-	}
 }
diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts
index 65c940a8..392d708e 100644
--- a/packages/happy-dom/src/nodes/element/Element.ts
+++ b/packages/happy-dom/src/nodes/element/Element.ts
@@ -1036,6 +1036,20 @@ export default class Element extends Node implements IElement {
 		this.scroll(x, y);
 	}
 
+	/**
+	 * @override
+	 */
+	public override dispatchEvent(event: Event): boolean {
+		const returnValue = super.dispatchEvent(event);
+		const attribute = this.getAttribute('on' + event.type);
+
+		if (attribute && !event._immediatePropagationStopped) {
+			this.ownerDocument.defaultView.eval(attribute);
+		}
+
+		return returnValue;
+	}
+
 	/**
 	 * Returns attribute name.
 	 *
diff --git a/packages/happy-dom/src/nodes/element/ElementUtility.ts b/packages/happy-dom/src/nodes/element/ElementUtility.ts
index 2ca50630..a30ce7c1 100644
--- a/packages/happy-dom/src/nodes/element/ElementUtility.ts
+++ b/packages/happy-dom/src/nodes/element/ElementUtility.ts
@@ -29,10 +29,9 @@ export default class ElementUtility {
 				if (index !== -1) {
 					for (const attribute of NAMED_ITEM_ATTRIBUTES) {
 						if ((<Element>node)._attributes[attribute]) {
-							(<HTMLCollection<IHTMLElement, IHTMLElement>>parentElement.children)._removeNamedItem(
-								<IHTMLElement>node,
-								(<Element>node)._attributes[attribute].value
-							);
+							(<HTMLCollection<IHTMLElement, IHTMLElement>>(
+								(<IElement>node.parentNode).children
+							))._removeNamedItem(<IHTMLElement>node, (<Element>node)._attributes[attribute].value);
 						}
 					}
 					(<IHTMLElement>node.parentNode).children.splice(index, 1);
@@ -97,15 +96,21 @@ export default class ElementUtility {
 				if (index !== -1) {
 					for (const attribute of NAMED_ITEM_ATTRIBUTES) {
 						if ((<Element>newNode)._attributes[attribute]) {
-							(<HTMLCollection<IHTMLElement, IHTMLElement>>parentElement.children)._removeNamedItem(
+							(<HTMLCollection<IHTMLElement, IHTMLElement>>(
+								(<IElement>newNode.parentNode).children
+							))._removeNamedItem(
 								<IHTMLElement>newNode,
 								(<Element>newNode)._attributes[attribute].value
 							);
 						}
 					}
+
+					(<IElement>newNode.parentNode).children.splice(index, 1);
 				}
 			}
 
+			// Node.ts will call appendChild() for the scenario where "referenceNode" is "null"
+
 			if (referenceNode) {
 				if (referenceNode.nodeType === NodeTypeEnum.elementNode) {
 					const index = parentElement.children.indexOf(<IElement>referenceNode);
@@ -122,7 +127,9 @@ export default class ElementUtility {
 						}
 					}
 				}
+			}
 
+			if (referenceNode || referenceNode === null) {
 				for (const attribute of NAMED_ITEM_ATTRIBUTES) {
 					if ((<Element>newNode)._attributes[attribute]) {
 						(<HTMLCollection<IHTMLElement, IHTMLElement>>parentElement.children)._appendNamedItem(
diff --git a/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts b/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts
index 4c57e813..18723f87 100644
--- a/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts
+++ b/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts
@@ -216,18 +216,14 @@ export default class HTMLButtonElement extends HTMLElement implements IHTMLButto
 
 		const returnValue = super.dispatchEvent(event);
 
-		if (event.type === 'click' && this.isConnected && !this.disabled) {
+		if (event.type === 'click' && this._formNode && this.isConnected) {
 			const form = <IHTMLFormElement>this._formNode;
 			switch (this.type) {
 				case 'submit':
-					if (form) {
-						form.requestSubmit();
-					}
+					form.requestSubmit();
 					break;
 				case 'reset':
-					if (form) {
-						form.reset();
-					}
+					form.reset();
 					break;
 			}
 		}
diff --git a/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts b/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts
index 4b99ed0a..317ebd0e 100644
--- a/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts
+++ b/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts
@@ -34,20 +34,6 @@ export default class HTMLTemplateElement extends HTMLElement implements IHTMLTem
 		}
 	}
 
-	/**
-	 * @override
-	 */
-	public get previousSibling(): INode {
-		return null;
-	}
-
-	/**
-	 * @override
-	 */
-	public get nextSibling(): INode {
-		return null;
-	}
-
 	/**
 	 * @override
 	 */
diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts
index 79974eab..0d0c5a95 100644
--- a/packages/happy-dom/src/nodes/node/Node.ts
+++ b/packages/happy-dom/src/nodes/node/Node.ts
@@ -491,10 +491,10 @@ export default class Node extends EventTarget implements INode {
 	/**
 	 * @override
 	 */
-	public dispatchEvent(event: Event): boolean {
+	public override dispatchEvent(event: Event): boolean {
 		const returnValue = super.dispatchEvent(event);
 
-		if (event.bubbles && !event._propagationStopped) {
+		if (event.bubbles && !event._propagationStopped && !event._immediatePropagationStopped) {
 			if (this.parentNode) {
 				return this.parentNode.dispatchEvent(event);
 			}


Test patch:
diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts
index ddf4b572..d9aade8b 100644
--- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts
+++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts
@@ -21,6 +21,7 @@ describe('CSSStyleDeclaration', () => {
 
 			element.setAttribute('style', `border: 2px solid green;border-radius: 2px;font-size: 12px;`);
 
+			expect(declaration.length).toBe(22);
 			expect(declaration[0]).toBe('border-top-width');
 			expect(declaration[1]).toBe('border-right-width');
 			expect(declaration[2]).toBe('border-bottom-width');
@@ -53,6 +54,7 @@ describe('CSSStyleDeclaration', () => {
 			declaration.borderRadius = '2px';
 			declaration.fontSize = '12px';
 
+			expect(declaration.length).toBe(22);
 			expect(declaration[0]).toBe('border-top-width');
 			expect(declaration[1]).toBe('border-right-width');
 			expect(declaration[2]).toBe('border-bottom-width');
@@ -85,6 +87,8 @@ describe('CSSStyleDeclaration', () => {
 
 			element.setAttribute('style', 'border: inherit');
 
+			expect(declaration.length).toBe(17);
+
 			expect(declaration.border).toBe('inherit');
 			expect(declaration.borderTop).toBe('inherit');
 			expect(declaration.borderRight).toBe('inherit');
@@ -194,6 +198,8 @@ describe('CSSStyleDeclaration', () => {
 
 			element.setAttribute('style', 'border-top: inherit');
 
+			expect(declaration.length).toBe(3);
+
 			expect(declaration.borderTop).toBe('inherit');
 			expect(declaration.borderTopColor).toBe('inherit');
 			expect(declaration.borderTopWidth).toBe('inherit');
diff --git a/packages/happy-dom/test/event/Event.test.ts b/packages/happy-dom/test/event/Event.test.ts
index 91bfe362..116886d8 100644
--- a/packages/happy-dom/test/event/Event.test.ts
+++ b/packages/happy-dom/test/event/Event.test.ts
@@ -20,6 +20,143 @@ describe('Event', () => {
 		jest.restoreAllMocks();
 	});
 
+	describe('get target()', () => {
+		it('Returns target.', () => {
+			const event = new Event('click', { bubbles: true });
+			expect(event.target === null).toBe(true);
+
+			const div = document.createElement('div');
+			const span = document.createElement('span');
+
+			div.appendChild(span);
+
+			span.dispatchEvent(event);
+
+			expect(event.target === span).toBe(true);
+		});
+	});
+
+	describe('get currentTarget()', () => {
+		it('Returns current target.', () => {
+			const event = new Event('click', { bubbles: true });
+			expect(event.currentTarget === null).toBe(true);
+
+			const div = document.createElement('div');
+			const span = document.createElement('span');
+
+			div.appendChild(span);
+
+			span.dispatchEvent(event);
+
+			expect(event.currentTarget === div).toBe(true);
+		});
+	});
+
+	describe('get cancelBubble()', () => {
+		it('Returns "true" if propagation has been stopped.', () => {
+			const event = new Event('click');
+			expect(event.cancelBubble).toBe(false);
+			event.stopPropagation();
+			expect(event.cancelBubble).toBe(true);
+		});
+	});
+
+	describe('initEvent()', () => {
+		it('Depracated way to init an event.', () => {
+			const event = new Event('click');
+			event.initEvent('newEventType', true, true);
+			expect(event.type).toBe('newEventType');
+			expect(event.bubbles).toBe(true);
+			expect(event.cancelable).toBe(true);
+		});
+	});
+
+	describe('preventDefault()', () => {
+		it('Prevents default behaviour.', () => {
+			const event = new Event('click');
+			event.preventDefault();
+			expect(event.defaultPrevented).toBe(true);
+		});
+
+		it("Doesn't prevent default if event is in a passive listener.", () => {
+			const event = new Event('click', { bubbles: true });
+			expect(event.currentTarget === null).toBe(true);
+
+			const div = document.createElement('div');
+			const span = document.createElement('span');
+
+			div.appendChild(span);
+
+			span.addEventListener(
+				'click',
+				(e: Event) => {
+					e.preventDefault();
+				},
+				{ passive: true }
+			);
+
+			span.dispatchEvent(event);
+
+			expect(event.defaultPrevented).toBe(false);
+
+			span.addEventListener('click', (e: Event) => {
+				e.preventDefault();
+			});
+
+			span.dispatchEvent(event);
+
+			expect(event.defaultPrevented).toBe(true);
+		});
+	});
+
+	describe('stopImmediatePropagation()', () => {
+		it('Stops any proceeding listener from beeing called.', () => {
+			const event = new Event('click', { bubbles: true });
+
+			expect(event.currentTarget === null).toBe(true);
+
+			const div = document.createElement('div');
+			const span = document.createElement('span');
+			let isSpanListenerCalled = false;
+			let isDivListenerCalled = false;
+
+			div.appendChild(span);
+
+			span.addEventListener('click', (e: Event) => e.stopImmediatePropagation());
+			span.addEventListener('click', () => (isSpanListenerCalled = true));
+			div.addEventListener('click', () => (isDivListenerCalled = true));
+
+			span.dispatchEvent(event);
+
+			expect(isSpanListenerCalled).toBe(false);
+			expect(isDivListenerCalled).toBe(false);
+		});
+	});
+
+	describe('stopPropagation()', () => {
+		it('Stops bubbling to the parent node.', () => {
+			const event = new Event('click', { bubbles: true });
+
+			expect(event.currentTarget === null).toBe(true);
+
+			const div = document.createElement('div');
+			const span = document.createElement('span');
+			let isSpanListenerCalled = false;
+			let isDivListenerCalled = false;
+
+			div.appendChild(span);
+
+			span.addEventListener('click', (e: Event) => e.stopPropagation());
+			span.addEventListener('click', () => (isSpanListenerCalled = true));
+			div.addEventListener('click', () => (isDivListenerCalled = true));
+
+			span.dispatchEvent(event);
+
+			expect(isSpanListenerCalled).toBe(true);
+			expect(isDivListenerCalled).toBe(false);
+		});
+	});
+
 	describe('get timeStamp()', () => {
 		it('Returns the value returned by performance.now() at the time it was created.', () => {
 			Object.defineProperty(performance, 'now', {
@@ -54,14 +191,13 @@ describe('Event', () => {
 				})
 			);
 
-			expect(composedPath).toEqual([
-				span,
-				div,
-				document.body,
-				document.documentElement,
-				document,
-				window
-			]);
+			expect(composedPath.length).toBe(6);
+			expect(composedPath[0] === span).toBe(true);
+			expect(composedPath[1] === div).toBe(true);
+			expect(composedPath[2] === document.body).toBe(true);
+			expect(composedPath[3] === document.documentElement).toBe(true);
+			expect(composedPath[4] === document).toBe(true);
+			expect(composedPath[5] === window).toBe(true);
 		});
 
 		it('Goes through shadow roots if composed is set to "true".', () => {
@@ -84,17 +220,16 @@ describe('Event', () => {
 				})
 			);
 
-			expect(composedPath).toEqual([
-				customELement.shadowRoot.children[1].children[0],
-				customELement.shadowRoot.children[1],
-				customELement.shadowRoot,
-				customELement,
-				div,
-				document.body,
-				document.documentElement,
-				document,
-				window
-			]);
+			expect(composedPath.length).toBe(9);
+			expect(composedPath[0] === customELement.shadowRoot.children[1].children[0]).toBe(true);
+			expect(composedPath[1] === customELement.shadowRoot.children[1]).toBe(true);
+			expect(composedPath[2] === customELement.shadowRoot).toBe(true);
+			expect(composedPath[3] === customELement).toBe(true);
+			expect(composedPath[4] === div).toBe(true);
+			expect(composedPath[5] === document.body).toBe(true);
+			expect(composedPath[6] === document.documentElement).toBe(true);
+			expect(composedPath[7] === document).toBe(true);
+			expect(composedPath[8] === window).toBe(true);
 		});
 
 		it('Does not go through shadow roots if composed is set to "false".', () => {
@@ -113,11 +248,10 @@ describe('Event', () => {
 				})
 			);
 
-			expect(composedPath).toEqual([
-				customELement.shadowRoot.children[1].children[0],
-				customELement.shadowRoot.children[1],
-				customELement.shadowRoot
-			]);
+			expect(composedPath.length).toBe(3);
+			expect(composedPath[0] === customELement.shadowRoot.children[1].children[0]).toBe(true);
+			expect(composedPath[1] === customELement.shadowRoot.children[1]).toBe(true);
+			expect(composedPath[2] === customELement.shadowRoot).toBe(true);
 		});
 
 		it('Returns correct composed for HTMLAnchorElement event target and composed is set to "true".', () => {
@@ -138,13 +272,12 @@ describe('Event', () => {
 				})
 			);
 
-			expect(composedPath).toEqual([
-				anchor,
-				document.body,
-				document.documentElement,
-				document,
-				window
-			]);
+			expect(composedPath.length).toBe(5);
+			expect(composedPath[0] === anchor).toBe(true);
+			expect(composedPath[1] === document.body).toBe(true);
+			expect(composedPath[2] === document.documentElement).toBe(true);
+			expect(composedPath[3] === document).toBe(true);
+			expect(composedPath[4] === window).toBe(true);
 		});
 	});
 });
diff --git a/packages/happy-dom/test/nodes/element/Element.test.ts b/packages/happy-dom/test/nodes/element/Element.test.ts
index 83c28aff..335aa3e4 100644
--- a/packages/happy-dom/test/nodes/element/Element.test.ts
+++ b/packages/happy-dom/test/nodes/element/Element.test.ts
@@ -18,6 +18,7 @@ import IHTMLCollection from '../../../src/nodes/element/IHTMLCollection';
 import IElement from '../../../src/nodes/element/IElement';
 import INodeList from '../../../src/nodes/node/INodeList';
 import IAttr from '../../../src/nodes/attr/IAttr';
+import Event from '../../../src/event/Event';
 
 const NAMESPACE_URI = 'https://test.test';
 
@@ -980,6 +981,56 @@ describe('Element', () => {
 			expect(clone.children.length).toBe(0);
 			expect(element.innerHTML).toBe('<div>Div</div><span>Span</span>');
 		});
+
+		it('Removes child from previous parent.', () => {
+			const otherParent = document.createElement('div');
+			const div = document.createElement('div');
+			const span = document.createElement('span');
+			const otherDiv = document.createElement('div');
+			const otherSpan = document.createElement('span');
+
+			div.setAttribute('id', 'div1');
+			div.setAttribute('name', 'div2');
+			span.setAttribute('id', 'span');
+			otherDiv.setAttribute('id', 'otherDiv');
+			otherSpan.setAttribute('id', 'otherSpan');
+
+			otherParent.appendChild(document.createComment('test'));
+			otherParent.appendChild(otherDiv);
+			otherParent.appendChild(document.createComment('test'));
+			otherParent.appendChild(div);
+			otherParent.appendChild(document.createComment('test'));
+			otherParent.appendChild(otherSpan);
+
+			expect(otherParent.children.length).toBe(3);
+			expect(otherParent.children[0] === otherDiv).toBe(true);
+			expect(otherParent.children[1] === div).toBe(true);
+			expect(otherParent.children[2] === otherSpan).toBe(true);
+			expect(otherParent.children['div1'] === div).toBe(true);
+			expect(otherParent.children['div2'] === div).toBe(true);
+			expect(otherParent.children['otherDiv'] === otherDiv).toBe(true);
+			expect(otherParent.children['otherSpan'] === otherSpan).toBe(true);
+
+			element.appendChild(document.createComment('test'));
+			element.appendChild(div);
+			element.appendChild(document.createComment('test'));
+			element.appendChild(span);
+
+			expect(otherParent.children.length).toBe(2);
+			expect(otherParent.children[0] === otherDiv).toBe(true);
+			expect(otherParent.children[1] === otherSpan).toBe(true);
+			expect(otherParent.children['div1'] === undefined).toBe(true);
+			expect(otherParent.children['div2'] === undefined).toBe(true);
+			expect(otherParent.children['otherDiv'] === otherDiv).toBe(true);
+			expect(otherParent.children['otherSpan'] === otherSpan).toBe(true);
+
+			expect(element.children.length).toBe(2);
+			expect(element.children[0] === div).toBe(true);
+			expect(element.children[1] === span).toBe(true);
+			expect(element.children['div1'] === div).toBe(true);
+			expect(element.children['div2'] === div).toBe(true);
+			expect(element.children['span'] === span).toBe(true);
+		});
 	});
 
 	describe('removeChild()', () => {
@@ -987,14 +1038,23 @@ describe('Element', () => {
 			const div = document.createElement('div');
 			const span = document.createElement('span');
 
+			div.setAttribute('name', 'div');
+			span.setAttribute('name', 'span');
+
 			element.appendChild(document.createComment('test'));
 			element.appendChild(div);
 			element.appendChild(document.createComment('test'));
 			element.appendChild(span);
 
+			expect(element.children['div'] === div).toBe(true);
+			expect(element.children['span'] === span).toBe(true);
+
 			element.removeChild(div);
 
-			expect(Array.from(element.children)).toEqual([span]);
+			expect(element.children.length).toBe(1);
+			expect(element.children[0] === span).toBe(true);
+			expect(element.children['div'] === undefined).toBe(true);
+			expect(element.children['span'] === span).toBe(true);
 		});
 	});
 
@@ -1036,6 +1096,59 @@ describe('Element', () => {
 				'<span></span><div>Template DIV 1</div><span>Template SPAN 1</span><span></span>'
 			);
 		});
+
+		it('Removes child from previous parent node when moved.', () => {
+			const div = document.createElement('div');
+			const span1 = document.createElement('span');
+			const span2 = document.createElement('span');
+			const otherParent = document.createElement('div');
+			const otherSpan1 = document.createElement('span');
+			const otherSpan2 = document.createElement('span');
+
+			div.setAttribute('id', 'div');
+			span1.setAttribute('id', 'span1');
+			span2.setAttribute('id', 'span2');
+			otherSpan1.setAttribute('id', 'otherSpan1');
+			otherSpan2.setAttribute('id', 'otherSpan2');
+
+			otherParent.appendChild(document.createComment('test'));
+			otherParent.appendChild(otherSpan1);
+			otherParent.appendChild(document.createComment('test'));
+			otherParent.appendChild(otherSpan2);
+			otherParent.insertBefore(div, otherSpan2);
+
+			expect(otherParent.children.length).toBe(3);
+			expect(otherParent.children[0] === otherSpan1).toBe(true);
+			expect(otherParent.children[1] === div).toBe(true);
+			expect(otherParent.children[2] === otherSpan2).toBe(true);
+			expect(otherParent.children['otherSpan1'] === otherSpan1).toBe(true);
+			expect(otherParent.children['div'] === div).toBe(true);
+			expect(otherParent.children['otherSpan2'] === otherSpan2).toBe(true);
+
+			element.appendChild(document.createComment('test'));
+			element.appendChild(span1);
+			element.appendChild(document.createComment('test'));
+			element.appendChild(document.createComment('test'));
+			element.appendChild(span2);
+			element.appendChild(document.createComment('test'));
+
+			element.insertBefore(div, span2);
+
+			expect(otherParent.children.length).toBe(2);
+			expect(otherParent.children[0] === otherSpan1).toBe(true);
+			expect(otherParent.children[1] === otherSpan2).toBe(true);
+			expect(otherParent.children['div'] === undefined).toBe(true);
+			expect(otherParent.children['otherSpan1'] === otherSpan1).toBe(true);
+			expect(otherParent.children['otherSpan2'] === otherSpan2).toBe(true);
+
+			expect(element.children.length).toBe(3);
+			expect(element.children[0] === span1).toBe(true);
+			expect(element.children[1] === div).toBe(true);
+			expect(element.children[2] === span2).toBe(true);
+			expect(element.children['span1'] === span1).toBe(true);
+			expect(element.children['div'] === div).toBe(true);
+			expect(element.children['span2'] === span2).toBe(true);
+		});
 	});
 
 	describe('get previousElementSibling()', () => {
@@ -1555,4 +1668,21 @@ describe('Element', () => {
 			expect(div.scrollTop).toBe(15);
 		});
 	});
+
+	describe('dispatchEvent()', () => {
+		it('Evaluates attribute event listeners.', () => {
+			const div = document.createElement('div');
+			div.setAttribute('onclick', 'divClicked = true');
+			div.dispatchEvent(new Event('click'));
+			expect(window['divClicked']).toBe(true);
+		});
+
+		it("Doesn't evaluate attribute event listener is immediate propagation has been stopped.", () => {
+			const div = document.createElement('div');
+			div.addEventListener('click', (e: Event) => e.stopImmediatePropagation());
+			div.setAttribute('onclick', 'divClicked = true');
+			div.dispatchEvent(new Event('click'));
+			expect(window['divClicked']).toBe(undefined);
+		});
+	});
 });
diff --git a/packages/happy-dom/test/nodes/html-template-element/HTMLTemplateElement.test.ts b/packages/happy-dom/test/nodes/html-template-element/HTMLTemplateElement.test.ts
index e5f804d6..61eda7a2 100644
--- a/packages/happy-dom/test/nodes/html-template-element/HTMLTemplateElement.test.ts
+++ b/packages/happy-dom/test/nodes/html-template-element/HTMLTemplateElement.test.ts
@@ -94,20 +94,6 @@ describe('HTMLTemplateElement', () => {
 		});
 	});
 
-	describe('get previousSibling()', () => {
-		it('Returns null.', () => {
-			element.innerHTML = '<div>Test</div>';
-			expect(element.previousSibling).toBe(null);
-		});
-	});
-
-	describe('get nextSibling()', () => {
-		it('Returns null.', () => {
-			element.innerHTML = '<div>Test</div>';
-			expect(element.nextSibling).toBe(null);
-		});
-	});
-
 	describe('get firstChild()', () => {
 		it('Returns first child.', () => {
 			const div = document.createElement('div');
diff --git a/packages/happy-dom/test/xml-parser/XMLParser.test.ts b/packages/happy-dom/test/xml-parser/XMLParser.test.ts
index 85dde35a..4295f787 100644
--- a/packages/happy-dom/test/xml-parser/XMLParser.test.ts
+++ b/packages/happy-dom/test/xml-parser/XMLParser.test.ts
@@ -4,11 +4,11 @@ import IWindow from '../../src/window/IWindow';
 import IDocument from '../../src/nodes/document/IDocument';
 import Node from '../../src/nodes/node/Node';
 import IHTMLElement from '../../src/nodes/html-element/IHTMLElement';
-import IHTMLTemplateElement from '../../src/nodes/html-template-element/IHTMLTemplateElement';
 import XMLParserHTML from './data/XMLParserHTML';
 import NamespaceURI from '../../src/config/NamespaceURI';
 import DocumentType from '../../src/nodes/document-type/DocumentType';
 import XMLSerializer from '../../src/xml-serializer/XMLSerializer';
+import IHTMLTemplateElement from '../../src/nodes/html-template-element/IHTMLTemplateElement';
 
 const GET_EXPECTED_HTML = (html: string): string =>
 	html
@@ -185,14 +185,13 @@ describe('XMLParser', () => {
 			);
 		});
 
-		it('Does not parse the content of script, style and template elements.', () => {
+		it('Does not parse the content of script and style elements.', () => {
 			const root = XMLParser.parse(
 				window.document,
 				`<div>
 					<script>if(1<Math['random']()){}else if(Math['random']()>1){console.log("1")}</script>
 					<script><b></b></script>
 					<style><b></b></style>
-					<template><b></b></template>
 				</div>`
 			);
 
@@ -202,16 +201,12 @@ describe('XMLParser', () => {
 
 			expect((<IHTMLElement>root.children[0].children[1]).innerText).toBe('<b></b>');
 			expect((<IHTMLElement>root.children[0].children[2]).innerText).toBe('<b></b>');
-			expect((<IHTMLTemplateElement>root.children[0].children[3]).content.textContent).toBe(
-				'<b></b>'
-			);
 
 			expect(new XMLSerializer().serializeToString(root).replace(/[\s]/gm, '')).toBe(
 				`<div>
 					<script>if(1<Math['random']()){}else if(Math['random']()>1){console.log("1")}</script>
 					<script><b></b></script>
 					<style><b></b></style>
-					<template><b></b></template>
 				</div>`.replace(/[\s]/gm, '')
 			);
 
@@ -463,5 +458,20 @@ describe('XMLParser', () => {
 				expect(new XMLSerializer().serializeToString(root)).toBe(html);
 			}
 		});
+
+		it('Parses <template> elements, including its content.', () => {
+			const root = XMLParser.parse(
+				window.document,
+				'<div><template><tr><td></td></tr></template></div>'
+			);
+			expect(root.childNodes.length).toBe(1);
+			const template = <IHTMLTemplateElement>root.childNodes[0].childNodes[0];
+			expect(template.childNodes.length).toBe(0);
+			expect(template.children.length).toBe(0);
+			expect(template.content.children.length).toBe(1);
+			expect(template.content.children[0].tagName).toBe('TR');
+			expect(template.content.children[0].children.length).toBe(1);
+			expect(template.content.children[0].children[0].tagName).toBe('TD');
+		});
 	});
 });