Repository: capricorn86/happy-dom
Pull request #210 (2021-02-12 11:09:32)

#208@minor: Adds support for HTMLCollection and NodeList.


Patch:
diff --git a/packages/happy-dom/src/dom-parser/DOMParser.ts b/packages/happy-dom/src/dom-parser/DOMParser.ts
index 59f91aee..44ee7364 100644
--- a/packages/happy-dom/src/dom-parser/DOMParser.ts
+++ b/packages/happy-dom/src/dom-parser/DOMParser.ts
@@ -1,7 +1,6 @@
 import Document from '../nodes/document/Document';
 import XMLParser from '../xml-parser/XMLParser';
 import Node from '../nodes/node/Node';
-import Element from '../nodes/element/Element';
 import DOMException from '../exception/DOMException';
 
 /**
@@ -30,9 +29,8 @@ export default class DOMParser {
 		const newDocument = new Document();
 
 		newDocument.defaultView = ownerDocument.defaultView;
-
-		(<Node[]>newDocument.childNodes) = [];
-		(<Element[]>newDocument.children) = [];
+		newDocument.childNodes.length = 0;
+		newDocument.children.length = 0;
 
 		const root = XMLParser.parse(newDocument, string, true);
 		let documentElement = null;
diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts
index d96a6471..085e2502 100644
--- a/packages/happy-dom/src/index.ts
+++ b/packages/happy-dom/src/index.ts
@@ -4,6 +4,7 @@ import Document from './nodes/document/Document';
 import IDocument from './nodes/document/IDocument';
 import Element from './nodes/element/Element';
 import IElement from './nodes/element/IElement';
+import IHTMLCollection from './nodes/element/IHTMLCollection';
 import HTMLElement from './nodes/html-element/HTMLElement';
 import IHTMLElement from './nodes/html-element/IHTMLElement';
 import HTMLTemplateElement from './nodes/html-template-element/HTMLTemplateElement';
@@ -26,6 +27,7 @@ import ShadowRoot from './nodes/shadow-root/ShadowRoot';
 import IShadowRoot from './nodes/shadow-root/IShadowRoot';
 import Node from './nodes/node/Node';
 import INode from './nodes/node/INode';
+import INodeList from './nodes/node/INodeList';
 import Text from './nodes/text/Text';
 import IText from './nodes/text/IText';
 import Comment from './nodes/comment/Comment';
@@ -48,6 +50,7 @@ export {
 	IDocument,
 	Element,
 	IElement,
+	IHTMLCollection,
 	HTMLElement,
 	IHTMLElement,
 	HTMLTemplateElement,
@@ -70,6 +73,7 @@ export {
 	IShadowRoot,
 	Node,
 	INode,
+	INodeList,
 	Text,
 	IText,
 	Comment,
diff --git a/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts b/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts
index 6847adb1..ff9dae5b 100644
--- a/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts
+++ b/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts
@@ -4,13 +4,15 @@ import QuerySelector from '../../query-selector/QuerySelector';
 import ParentNodeUtility from '../parent-node/ParentNodeUtility';
 import IDocumentFragment from './IDocumentFragment';
 import INode from '../node/INode';
+import IHTMLCollection from '../element/IHTMLCollection';
+import HTMLCollectionFactory from '../element/HTMLCollectionFactory';
 
 /**
  * DocumentFragment.
  */
 export default class DocumentFragment extends Node implements IDocumentFragment {
 	public nodeType = Node.DOCUMENT_FRAGMENT_NODE;
-	public readonly children: IElement[] = [];
+	public readonly children: IHTMLCollection<IElement> = HTMLCollectionFactory.create();
 
 	/**
 	 * Last element child.
@@ -134,11 +136,11 @@ export default class DocumentFragment extends Node implements IDocumentFragment
 		const clone = <IDocumentFragment>super.cloneNode(deep);
 
 		if (deep) {
-			const children = <IElement[]>(
-				clone.childNodes.filter(node => node.nodeType === Node.ELEMENT_NODE)
-			);
-
-			(<IElement[]>clone.children) = children;
+			for (const node of clone.childNodes) {
+				if (node.nodeType === Node.ELEMENT_NODE) {
+					clone.children.push(<IElement>node);
+				}
+			}
 		}
 
 		return <IDocumentFragment>clone;
@@ -208,9 +210,13 @@ export default class DocumentFragment extends Node implements IDocumentFragment
 				}
 			}
 
-			(<IElement[]>this.children) = <IElement[]>(
-				this.childNodes.filter(node => node.nodeType === Node.ELEMENT_NODE)
-			);
+			this.children.length = 0;
+
+			for (const node of this.childNodes) {
+				if (node.nodeType === Node.ELEMENT_NODE) {
+					this.children.push(<IElement>node);
+				}
+			}
 		}
 
 		return returnValue;
diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts
index d736a389..cc82df46 100644
--- a/packages/happy-dom/src/nodes/document/Document.ts
+++ b/packages/happy-dom/src/nodes/document/Document.ts
@@ -26,6 +26,9 @@ import IDocumentType from '../document-type/IDocumentType';
 import INode from '../node/INode';
 import ICharacterData from '../character-data/ICharacterData';
 import IDocumentFragment from '../document-fragment/IDocumentFragment';
+import INodeList from '../node/INodeList';
+import IHTMLCollection from '../element/IHTMLCollection';
+import HTMLCollectionFactory from '../element/HTMLCollectionFactory';
 
 /**
  * Document.
@@ -38,7 +41,7 @@ export default class Document extends Node implements IDocument {
 	protected _isFirstWrite = true;
 	protected _isFirstWriteAfterOpen = false;
 	public implementation: DOMImplementation;
-	public readonly children: Element[] = [];
+	public readonly children: IHTMLCollection<IElement> = HTMLCollectionFactory.create();
 	private _cookie = '';
 
 	/**
@@ -190,7 +193,7 @@ export default class Document extends Node implements IDocument {
 	 * @param selector CSS selector.
 	 * @returns Matching elements.
 	 */
-	public querySelectorAll(selector: string): IElement[] {
+	public querySelectorAll(selector: string): INodeList<IElement> {
 		return QuerySelector.querySelectorAll(this, selector);
 	}
 
@@ -210,8 +213,8 @@ export default class Document extends Node implements IDocument {
 	 * @param className Tag name.
 	 * @returns Matching element.
 	 */
-	public getElementsByClassName(className: string): IElement[] {
-		return <Element[]>ParentNodeUtility.getElementsByClassName(this, className);
+	public getElementsByClassName(className: string): IHTMLCollection<IElement> {
+		return ParentNodeUtility.getElementsByClassName(this, className);
 	}
 
 	/**
@@ -220,8 +223,8 @@ export default class Document extends Node implements IDocument {
 	 * @param tagName Tag name.
 	 * @returns Matching element.
 	 */
-	public getElementsByTagName(tagName: string): IElement[] {
-		return <Element[]>ParentNodeUtility.getElementsByTagName(this, tagName);
+	public getElementsByTagName(tagName: string): IHTMLCollection<IElement> {
+		return ParentNodeUtility.getElementsByTagName(this, tagName);
 	}
 
 	/**
@@ -231,8 +234,8 @@ export default class Document extends Node implements IDocument {
 	 * @param tagName Tag name.
 	 * @returns Matching element.
 	 */
-	public getElementsByTagNameNS(namespaceURI: string, tagName: string): IElement[] {
-		return <Element[]>ParentNodeUtility.getElementsByTagNameNS(this, namespaceURI, tagName);
+	public getElementsByTagNameNS(namespaceURI: string, tagName: string): IHTMLCollection<IElement> {
+		return ParentNodeUtility.getElementsByTagNameNS(this, namespaceURI, tagName);
 	}
 
 	/**
@@ -256,9 +259,11 @@ export default class Document extends Node implements IDocument {
 		const clone = <Document>super.cloneNode(deep);
 
 		if (deep) {
-			(<Element[]>clone.children) = <Element[]>(
-				clone.childNodes.filter(node => node.nodeType === Node.ELEMENT_NODE)
-			);
+			for (const node of clone.childNodes) {
+				if (node.nodeType === Node.ELEMENT_NODE) {
+					clone.children.push(<IElement>node);
+				}
+			}
 		}
 
 		clone.defaultView = this.defaultView;
@@ -329,9 +334,13 @@ export default class Document extends Node implements IDocument {
 				}
 			}
 
-			(<Element[]>this.children) = <Element[]>(
-				this.childNodes.filter(node => node.nodeType === Node.ELEMENT_NODE)
-			);
+			this.children.length = 0;
+
+			for (const node of this.childNodes) {
+				if (node.nodeType === Node.ELEMENT_NODE) {
+					this.children.push(<IElement>node);
+				}
+			}
 		}
 
 		return returnValue;
diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts
index d552ee5b..0a8b382b 100644
--- a/packages/happy-dom/src/nodes/element/Element.ts
+++ b/packages/happy-dom/src/nodes/element/Element.ts
@@ -19,6 +19,9 @@ import DOMException from '../../exception/DOMException';
 import IShadowRoot from '../shadow-root/IShadowRoot';
 import INode from '../node/INode';
 import IDocument from '../document/IDocument';
+import IHTMLCollection from './IHTMLCollection';
+import INodeList from '../node/INodeList';
+import HTMLCollectionFactory from './HTMLCollectionFactory';
 
 /**
  * Element.
@@ -30,7 +33,7 @@ export default class Element extends Node implements IElement {
 	public readonly classList = new ClassList(this);
 	public scrollTop = 0;
 	public scrollLeft = 0;
-	public children: IElement[] = [];
+	public children: IHTMLCollection<IElement> = HTMLCollectionFactory.create();
 	public _attributes: { [k: string]: Attr } = {};
 	public readonly namespaceURI: string = null;
 	public static _observedAttributes: string[] = null;
@@ -255,9 +258,11 @@ export default class Element extends Node implements IElement {
 		}
 
 		if (deep) {
-			(<IElement[]>clone.children) = <IElement[]>(
-				clone.childNodes.filter(node => node.nodeType === Node.ELEMENT_NODE)
-			);
+			for (const node of clone.childNodes) {
+				if (node.nodeType === Node.ELEMENT_NODE) {
+					clone.children.push(<IElement>node);
+				}
+			}
 		}
 
 		(<string>clone.tagName) = this.tagName;
@@ -339,9 +344,13 @@ export default class Element extends Node implements IElement {
 				}
 			}
 
-			(<IElement[]>this.children) = <IElement[]>(
-				this.childNodes.filter(node => node.nodeType === Node.ELEMENT_NODE)
-			);
+			this.children.length = 0;
+
+			for (const node of this.childNodes) {
+				if (node.nodeType === Node.ELEMENT_NODE) {
+					this.children.push(<IElement>node);
+				}
+			}
 		}
 
 		return returnValue;
@@ -579,7 +588,7 @@ export default class Element extends Node implements IElement {
 	 * @param selector CSS selector.
 	 * @returns Matching elements.
 	 */
-	public querySelectorAll(selector: string): IElement[] {
+	public querySelectorAll(selector: string): INodeList<IElement> {
 		return QuerySelector.querySelectorAll(this, selector);
 	}
 
@@ -599,7 +608,7 @@ export default class Element extends Node implements IElement {
 	 * @param className Tag name.
 	 * @returns Matching element.
 	 */
-	public getElementsByClassName(className: string): IElement[] {
+	public getElementsByClassName(className: string): IHTMLCollection<IElement> {
 		return ParentNodeUtility.getElementsByClassName(this, className);
 	}
 
@@ -609,7 +618,7 @@ export default class Element extends Node implements IElement {
 	 * @param tagName Tag name.
 	 * @returns Matching element.
 	 */
-	public getElementsByTagName(tagName: string): IElement[] {
+	public getElementsByTagName(tagName: string): IHTMLCollection<IElement> {
 		return ParentNodeUtility.getElementsByTagName(this, tagName);
 	}
 
@@ -620,7 +629,7 @@ export default class Element extends Node implements IElement {
 	 * @param tagName Tag name.
 	 * @returns Matching element.
 	 */
-	public getElementsByTagNameNS(namespaceURI: string, tagName: string): IElement[] {
+	public getElementsByTagNameNS(namespaceURI: string, tagName: string): IHTMLCollection<IElement> {
 		return ParentNodeUtility.getElementsByTagNameNS(this, namespaceURI, tagName);
 	}
 
diff --git a/packages/happy-dom/src/nodes/element/HTMLCollectionFactory.ts b/packages/happy-dom/src/nodes/element/HTMLCollectionFactory.ts
new file mode 100644
index 00000000..f20e0630
--- /dev/null
+++ b/packages/happy-dom/src/nodes/element/HTMLCollectionFactory.ts
@@ -0,0 +1,26 @@
+import IElement from './IElement';
+import IHTMLCollection from './IHTMLCollection';
+
+export default class HTMLCollectionFactory {
+	/**
+	 * Creates an HTMLCollection.
+	 * @param nodes Nodes.
+	 * @returns HTMLCollection.
+	 */
+	public static create(nodes?: IElement[]): IHTMLCollection<IElement> {
+		nodes = nodes ? nodes.slice() : [];
+		Object.defineProperty(nodes, 'item', {
+			value: this.getItem.bind(null, nodes)
+		});
+		return <IHTMLCollection<IElement>>nodes;
+	}
+
+	/**
+	 * Returns node by index.
+	 *
+	 * @param index Index.
+	 */
+	private static getItem(nodes: IElement[], index: number): IElement {
+		return nodes[index] || null;
+	}
+}
diff --git a/packages/happy-dom/src/nodes/element/IElement.ts b/packages/happy-dom/src/nodes/element/IElement.ts
index f72127f5..b2da9daa 100644
--- a/packages/happy-dom/src/nodes/element/IElement.ts
+++ b/packages/happy-dom/src/nodes/element/IElement.ts
@@ -149,31 +149,6 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode,
 	 */
 	matches(selector: string): boolean;
 
-	/**
-	 * Returns an elements by tag name.
-	 *
-	 * @param tagName Tag name.
-	 * @returns Matching elements.
-	 */
-	getElementsByTagName(tagName: string): IElement[];
-
-	/**
-	 * Returns an elements by tag name.
-	 *
-	 * @param namespaceURI Namespace URI.
-	 * @param tagName Tag name.
-	 * @returns Matching nodes.
-	 */
-	getElementsByTagNameNS(namespaceURI: string, tagName: string): IElement[];
-
-	/**
-	 * Returns an elements by class name.
-	 *
-	 * @param className Tag name.
-	 * @returns Matching nodes.
-	 */
-	getElementsByClassName(className: string): IElement[];
-
 	/**
 	 * The setAttributeNode() method adds a new Attr node to the specified element.
 	 *
diff --git a/packages/happy-dom/src/nodes/element/IHTMLCollection.ts b/packages/happy-dom/src/nodes/element/IHTMLCollection.ts
new file mode 100644
index 00000000..75ebf539
--- /dev/null
+++ b/packages/happy-dom/src/nodes/element/IHTMLCollection.ts
@@ -0,0 +1,11 @@
+/**
+ * HTMLCollection.
+ */
+export default interface IHTMLCollection<T> extends Array<T> {
+	/**
+	 * Returns item by index.
+	 *
+	 * @param index Index.
+	 */
+	item(index: number): T;
+}
diff --git a/packages/happy-dom/src/nodes/node/INodeList.ts b/packages/happy-dom/src/nodes/node/INodeList.ts
new file mode 100644
index 00000000..f5d43acc
--- /dev/null
+++ b/packages/happy-dom/src/nodes/node/INodeList.ts
@@ -0,0 +1,11 @@
+/**
+ * NodeList.
+ */
+export default interface INodeList<T> extends Array<T> {
+	/**
+	 * Returns item by index.
+	 *
+	 * @param index Index.
+	 */
+	item(index: number): T;
+}
diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts
index 17047260..908d34ca 100644
--- a/packages/happy-dom/src/nodes/node/Node.ts
+++ b/packages/happy-dom/src/nodes/node/Node.ts
@@ -7,6 +7,8 @@ import INode from './INode';
 import DOMException from '../../exception/DOMException';
 import IDocument from '../document/IDocument';
 import IElement from '../element/IElement';
+import INodeList from './INodeList';
+import NodeListFactory from './NodeListFactory';
 
 /**
  * Node
@@ -23,7 +25,7 @@ export default class Node extends EventTarget implements INode {
 	public readonly ownerDocument: IDocument = null;
 	public readonly parentNode: INode = null;
 	public readonly nodeType: number;
-	public readonly childNodes: INode[] = [];
+	public readonly childNodes: INodeList<INode> = NodeListFactory.create();
 
 	// Protected properties
 	protected _isConnected = false;
diff --git a/packages/happy-dom/src/nodes/node/NodeListFactory.ts b/packages/happy-dom/src/nodes/node/NodeListFactory.ts
new file mode 100644
index 00000000..71840776
--- /dev/null
+++ b/packages/happy-dom/src/nodes/node/NodeListFactory.ts
@@ -0,0 +1,26 @@
+import INode from './INode';
+import INodeList from './INodeList';
+
+export default class NodeListFactory {
+	/**
+	 * Creates a NodeList.
+	 * @param nodes Nodes.
+	 * @returns NodeList.
+	 */
+	public static create(nodes?: INode[]): INodeList<INode> {
+		nodes = nodes ? nodes.slice() : [];
+		Object.defineProperty(nodes, 'item', {
+			value: this.getItem.bind(null, nodes)
+		});
+		return <INodeList<INode>>nodes;
+	}
+
+	/**
+	 * Returns node by index.
+	 *
+	 * @param index Index.
+	 */
+	private static getItem(nodes: INode[], index: number): INode {
+		return nodes[index] || null;
+	}
+}
diff --git a/packages/happy-dom/src/nodes/parent-node/IParentNode.ts b/packages/happy-dom/src/nodes/parent-node/IParentNode.ts
index 016d3cd6..4f38db3d 100644
--- a/packages/happy-dom/src/nodes/parent-node/IParentNode.ts
+++ b/packages/happy-dom/src/nodes/parent-node/IParentNode.ts
@@ -1,5 +1,7 @@
+import IHTMLCollection from '../element/IHTMLCollection';
 import IElement from '../element/IElement';
 import INode from '../node/INode';
+import INodeList from '../node/INodeList';
 
 export default interface IParentNode extends INode {
 	readonly childElementCount: number;
@@ -35,7 +37,7 @@ export default interface IParentNode extends INode {
 	 * @param selector CSS selector.
 	 * @returns Matching elements.
 	 */
-	querySelectorAll(selector: string): IElement[];
+	querySelectorAll(selector: string): INodeList<IElement>;
 
 	/**
 	 * Returns an elements by class name.
@@ -43,7 +45,7 @@ export default interface IParentNode extends INode {
 	 * @param className Tag name.
 	 * @returns Matching element.
 	 */
-	getElementsByClassName(className: string): IElement[];
+	getElementsByClassName(className: string): IHTMLCollection<IElement>;
 
 	/**
 	 * Returns an elements by tag name.
@@ -51,7 +53,7 @@ export default interface IParentNode extends INode {
 	 * @param tagName Tag name.
 	 * @returns Matching element.
 	 */
-	getElementsByTagName(tagName: string): IElement[];
+	getElementsByTagName(tagName: string): IHTMLCollection<IElement>;
 
 	/**
 	 * Returns an elements by tag name and namespace.
@@ -60,7 +62,7 @@ export default interface IParentNode extends INode {
 	 * @param tagName Tag name.
 	 * @returns Matching element.
 	 */
-	getElementsByTagNameNS(namespaceURI: string, tagName: string): IElement[];
+	getElementsByTagNameNS(namespaceURI: string, tagName: string): IHTMLCollection<IElement>;
 
 	/**
 	 * Replaces the existing children of a node with a specified new set of children.
diff --git a/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts b/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts
index 0933fb5d..d42ce5a6 100644
--- a/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts
+++ b/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts
@@ -3,7 +3,9 @@ import XMLParser from '../../xml-parser/XMLParser';
 import IDocumentFragment from '../document-fragment/IDocumentFragment';
 import IDocument from '../document/IDocument';
 import IElement from '../element/IElement';
+import IHTMLCollection from '../element/IHTMLCollection';
 import INode from '../node/INode';
+import HTMLCollectionFactory from '../element/HTMLCollectionFactory';
 
 /**
  * Parent node utility.
@@ -75,7 +77,10 @@ export default class ParentNodeUtility {
 	 * @param className Tag name.
 	 * @returns Matching element.
 	 */
-	public static getElementsByClassName(parentNode: INode, className: string): IElement[] {
+	public static getElementsByClassName(
+		parentNode: INode,
+		className: string
+	): IHTMLCollection<IElement> {
 		return QuerySelector.querySelectorAll(parentNode, '.' + className.split(' ').join('.'));
 	}
 
@@ -89,15 +94,17 @@ export default class ParentNodeUtility {
 	public static getElementsByTagName(
 		parentNode: IElement | IDocumentFragment | IDocument,
 		tagName: string
-	): IElement[] {
+	): IHTMLCollection<IElement> {
 		const upperTagName = tagName.toUpperCase();
-		let matches = [];
+		const matches = HTMLCollectionFactory.create();
 
 		for (const child of parentNode.children) {
 			if (child.tagName === upperTagName) {
 				matches.push(child);
 			}
-			matches = matches.concat(this.getElementsByTagName(<IElement>child, tagName));
+			for (const match of this.getElementsByTagName(<IElement>child, tagName)) {
+				matches.push(match);
+			}
 		}
 
 		return matches;
@@ -115,15 +122,17 @@ export default class ParentNodeUtility {
 		parentNode: IElement | IDocumentFragment | IDocument,
 		namespaceURI: string,
 		tagName: string
-	): IElement[] {
+	): IHTMLCollection<IElement> {
 		const upperTagName = tagName.toUpperCase();
-		let matches = [];
+		const matches = HTMLCollectionFactory.create();
 
 		for (const child of parentNode.children) {
 			if (child.tagName === upperTagName && child.namespaceURI === namespaceURI) {
 				matches.push(child);
 			}
-			matches = matches.concat(this.getElementsByTagNameNS(<IElement>child, namespaceURI, tagName));
+			for (const match of this.getElementsByTagNameNS(<IElement>child, namespaceURI, tagName)) {
+				matches.push(match);
+			}
 		}
 
 		return matches;
diff --git a/packages/happy-dom/src/query-selector/QuerySelector.ts b/packages/happy-dom/src/query-selector/QuerySelector.ts
index 1a51fb9c..ffbafbd1 100644
--- a/packages/happy-dom/src/query-selector/QuerySelector.ts
+++ b/packages/happy-dom/src/query-selector/QuerySelector.ts
@@ -2,7 +2,9 @@ import Element from '../nodes/element/Element';
 import IElement from '../nodes/element/IElement';
 import INode from '../nodes/node/INode';
 import Node from '../nodes/node/Node';
+import INodeList from '../nodes/node/INodeList';
 import SelectorItem from './SelectorItem';
+import NodeListFactory from '../nodes/node/NodeListFactory';
 
 const SELECTOR_PART_REGEXP = /(\[[^\]]+\]|[a-zA-Z0-9-_.#"*:()\]]+)|([ ,>]+)/g;
 
@@ -22,18 +24,18 @@ export default class QuerySelector {
 	 * @param selector Selector.
 	 * @returns HTML elements.
 	 */
-	public static querySelectorAll(node: INode, selector: string): IElement[] {
-		const matched = [];
+	public static querySelectorAll(node: INode, selector: string): INodeList<IElement> {
+		const matches = <INodeList<IElement>>NodeListFactory.create();
 
 		for (const parts of this.getSelectorParts(selector)) {
 			for (const element of this.findAll(node, [node], parts)) {
-				if (!matched.includes(element)) {
-					matched.push(element);
+				if (!matches.includes(element)) {
+					matches.push(element);
 				}
 			}
 		}
 
-		return matched;
+		return matches;
 	}
 
 	/**


Test patch:
diff --git a/packages/happy-dom/test/nodes/document-fragment/DocumentFragment.test.ts b/packages/happy-dom/test/nodes/document-fragment/DocumentFragment.test.ts
index b5d55cb2..2fd7b8c9 100644
--- a/packages/happy-dom/test/nodes/document-fragment/DocumentFragment.test.ts
+++ b/packages/happy-dom/test/nodes/document-fragment/DocumentFragment.test.ts
@@ -4,6 +4,8 @@ import ParentNodeUtility from '../../../src/nodes/parent-node/ParentNodeUtility'
 import QuerySelector from '../../../src/query-selector/QuerySelector';
 import HTMLTemplateElement from '../../../src/nodes/html-template-element/HTMLTemplateElement';
 import Text from '../../../src/nodes/text/Text';
+import INodeList from '../../../src/nodes/node/INodeList';
+import IElement from '../../../src/nodes/element/IElement';
 
 describe('DocumentFragment', () => {
 	let window, document, documentFragment;
@@ -157,7 +159,7 @@ describe('DocumentFragment', () => {
 			jest.spyOn(QuerySelector, 'querySelectorAll').mockImplementation((parentNode, selector) => {
 				expect(parentNode).toBe(documentFragment);
 				expect(selector).toEqual(expectedSelector);
-				return [element];
+				return <INodeList<IElement>>[element];
 			});
 
 			expect(documentFragment.querySelectorAll(expectedSelector)).toEqual([element]);
diff --git a/packages/happy-dom/test/nodes/document/Document.test.ts b/packages/happy-dom/test/nodes/document/Document.test.ts
index 20d600b8..2b0ae5cf 100644
--- a/packages/happy-dom/test/nodes/document/Document.test.ts
+++ b/packages/happy-dom/test/nodes/document/Document.test.ts
@@ -16,6 +16,9 @@ import ParentNodeUtility from '../../../src/nodes/parent-node/ParentNodeUtility'
 import QuerySelector from '../../../src/query-selector/QuerySelector';
 import NodeFilter from '../../../src/tree-walker/NodeFilter';
 import HTMLTemplateElement from '../../../src/nodes/html-template-element/HTMLTemplateElement';
+import IHTMLCollection from '../../../src/nodes/element/IHTMLCollection';
+import IElement from '../../../src/nodes/element/IElement';
+import INodeList from '../../../src/nodes/node/INodeList';
 
 describe('Document', () => {
 	let window: Window;
@@ -256,7 +259,7 @@ describe('Document', () => {
 			jest.spyOn(QuerySelector, 'querySelectorAll').mockImplementation((parentNode, selector) => {
 				expect(parentNode).toBe(document);
 				expect(selector).toEqual(expectedSelector);
-				return [element];
+				return <INodeList<IElement>>[element];
 			});
 
 			expect(document.querySelectorAll(expectedSelector)).toEqual([element]);
@@ -288,7 +291,7 @@ describe('Document', () => {
 				.mockImplementation((parentNode, requestedClassName) => {
 					expect(parentNode).toBe(document);
 					expect(requestedClassName).toEqual(className);
-					return [element];
+					return <IHTMLCollection<IElement>>[element];
 				});
 
 			expect(document.getElementsByClassName(className)).toEqual([element]);
@@ -305,7 +308,7 @@ describe('Document', () => {
 				.mockImplementation((parentNode, requestedTagName) => {
 					expect(parentNode).toBe(document);
 					expect(requestedTagName).toEqual(tagName);
-					return [element];
+					return <IHTMLCollection<IElement>>[element];
 				});
 
 			expect(document.getElementsByTagName(tagName)).toEqual([element]);
@@ -324,7 +327,7 @@ describe('Document', () => {
 					expect(parentNode).toBe(document);
 					expect(requestedNamespaceURI).toEqual(namespaceURI);
 					expect(requestedTagName).toEqual(tagName);
-					return [element];
+					return <IHTMLCollection<IElement>>[element];
 				});
 
 			expect(document.getElementsByTagNameNS(namespaceURI, tagName)).toEqual([element]);
diff --git a/packages/happy-dom/test/nodes/element/Element.test.ts b/packages/happy-dom/test/nodes/element/Element.test.ts
index 6c890605..0b6c0b8f 100644
--- a/packages/happy-dom/test/nodes/element/Element.test.ts
+++ b/packages/happy-dom/test/nodes/element/Element.test.ts
@@ -14,6 +14,9 @@ import QuerySelector from '../../../src/query-selector/QuerySelector';
 import ChildNodeUtility from '../../../src/nodes/child-node/ChildNodeUtility';
 import NonDocumentChildNodeUtility from '../../../src/nodes/child-node/NonDocumentChildNodeUtility';
 import HTMLTemplateElement from '../../../src/nodes/html-template-element/HTMLTemplateElement';
+import IHTMLCollection from '../../../src/nodes/element/IHTMLCollection';
+import IElement from '../../../src/nodes/element/IElement';
+import INodeList from '../../../src/nodes/node/INodeList';
 
 const NAMESPACE_URI = 'https://test.test';
 
@@ -336,7 +339,7 @@ describe('Element', () => {
 			jest.spyOn(QuerySelector, 'querySelectorAll').mockImplementation((parentNode, selector) => {
 				expect(parentNode).toBe(document);
 				expect(selector).toEqual(expectedSelector);
-				return [element];
+				return <INodeList<IElement>>[element];
 			});
 
 			expect(document.querySelectorAll(expectedSelector)).toEqual([element]);
@@ -368,7 +371,7 @@ describe('Element', () => {
 				.mockImplementation((parentNode, requestedClassName) => {
 					expect(parentNode).toBe(element);
 					expect(requestedClassName).toEqual(className);
-					return [child];
+					return <IHTMLCollection<IElement>>[child];
 				});
 
 			expect(element.getElementsByClassName(className)).toEqual([child]);
@@ -385,7 +388,7 @@ describe('Element', () => {
 				.mockImplementation((parentNode, requestedTagName) => {
 					expect(parentNode).toBe(element);
 					expect(requestedTagName).toEqual(tagName);
-					return [child];
+					return <IHTMLCollection<IElement>>[child];
 				});
 
 			expect(element.getElementsByTagName(tagName)).toEqual([child]);
@@ -404,7 +407,7 @@ describe('Element', () => {
 					expect(parentNode).toBe(element);
 					expect(requestedNamespaceURI).toEqual(namespaceURI);
 					expect(requestedTagName).toEqual(tagName);
-					return [child];
+					return <IHTMLCollection<IElement>>[child];
 				});
 
 			expect(element.getElementsByTagNameNS(namespaceURI, tagName)).toEqual([child]);
diff --git a/packages/happy-dom/test/nodes/parent-node/ParentNodeUtility.test.ts b/packages/happy-dom/test/nodes/parent-node/ParentNodeUtility.test.ts
index 7edfc28c..5534b4f3 100644
--- a/packages/happy-dom/test/nodes/parent-node/ParentNodeUtility.test.ts
+++ b/packages/happy-dom/test/nodes/parent-node/ParentNodeUtility.test.ts
@@ -3,6 +3,8 @@ import Document from '../../../src/nodes/document/Document';
 import ParentNodeUtility from '../../../src/nodes/parent-node/ParentNodeUtility';
 import QuerySelector from '../../../src/query-selector/QuerySelector';
 import NamespaceURI from '../../../src/config/NamespaceURI';
+import IHTMLCollection from '../../../src/nodes/element/IHTMLCollection';
+import IElement from '../../../src/nodes/element/IElement';
 
 describe('ParentNodeUtility', () => {
 	let window: Window;
@@ -120,7 +122,7 @@ describe('ParentNodeUtility', () => {
 			jest.spyOn(QuerySelector, 'querySelectorAll').mockImplementation((parentNode, selector) => {
 				expect(parentNode).toBe(parent);
 				expect(selector).toEqual(`.${className}`);
-				return [element];
+				return <IHTMLCollection<IElement>>[element];
 			});
 
 			expect(ParentNodeUtility.getElementsByClassName(parent, className)).toEqual([element]);
diff --git a/packages/happy-dom/test/query-selector/QuerySelector.test.ts b/packages/happy-dom/test/query-selector/QuerySelector.test.ts
index 39dd2cdf..f1900d90 100644
--- a/packages/happy-dom/test/query-selector/QuerySelector.test.ts
+++ b/packages/happy-dom/test/query-selector/QuerySelector.test.ts
@@ -1,3 +1,4 @@
+import IHTMLElement from '../../src/nodes/html-element/IHTMLElement';
 import Window from '../../src/window/Window';
 import QuerySelectorHTML from './data/QuerySelectorHTML';
 import QuerySelectorNthChildHTML from './data/QuerySelectorNthChildHTML';
@@ -20,6 +21,13 @@ describe('QuerySelector', () => {
 			expect(elements[1]).toBe(container.children[0].children[1].children[1]);
 		});
 
+		test('Returns a NodeList with the method item().', () => {
+			const container = <IHTMLElement>document.createElement('div');
+			container.innerHTML = QuerySelectorHTML;
+			const elements = container.querySelectorAll('span');
+			expect(elements.item(0)).toBe(container.children[0].children[1].children[0]);
+		});
+
 		test('Returns all h1 (heading 1) elements.', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;