Repository: capricorn86/happy-dom (TypeScript)

Issue:
`:scope` no longer works
**Describe the bug**
since `happy-dom@13`, `Element` methods with selector string argument seem to no more support `:scope` selector
* `.queySelector()`
* `.querySelectorAll()`
* `.matches()`

**To Reproduce**
Steps to reproduce the behavior:
1. `npm i happy-dom`
2. create this file:
```js
//index.js
import { Window } from 'happy-dom';
const window = new Window();
console.log(window.document.body.matches(':scope')); // false :(
```
3. `node index.js`

**Expected behavior**
`element.matches(':scope')` should be `true`

**Device:**
 - OS: linux
 - "Browser" happy-dem
 - Version @15.11.6




Discussion: Thank you for reporting @yukulele!

There was no support for `:scope`, but query selectors returned it as a match even though it wasn't supported. This has finally been fixed by adding support for it.
Thanks for your work on this @capricorn86. Unfortunately, I'm still seeing some inconsistent behavior with the `:scope` selector in happy-dom. I have a minimal reproduction in this repo here: https://github.com/devmattrick/happy-dom-scope

Here's what the output is in browsers:
<img width="441" height="139" alt="Image" src="https://github.com/user-attachments/assets/a71208d3-183c-4345-9e6c-c087803254cf" />

Here's the output in jsdom:
```
$ npm run test:jsdom

> happy-dom-scope@1.0.0 test:jsdom
> node -r global-jsdom/register test.js

Number of direct child elements: 1
```

And here's the output in happy-dom:
```
$ npm run test:happy-dom

> happy-dom-scope@1.0.0 test:happy-dom
> node -r ./happy-dom.js test.js

Number of direct child elements: 0
```

Let me know if you have any questions about this or would like me to file a separate issue.
Thank you @devmattrick and thank you for providing with a reproducible repo 🙂 

 I believe the issue should be fixed now.
@capricorn86 of course! I appreciate the fast turnaround!



Fix (patch):
diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts
index 241d86b91..bf6d67fcd 100644
--- a/packages/happy-dom/src/nodes/document/Document.ts
+++ b/packages/happy-dom/src/nodes/document/Document.ts
@@ -1472,7 +1472,7 @@ export default class Document extends Node {
 	): NodeList<IHTMLElementTagNameMap[K]>;
 
 	/**
-	 * Query CSS selector to find matching elments.
+	 * Query CSS selector to find matching elements.
 	 *
 	 * @param selector CSS selector.
 	 * @returns Matching elements.
@@ -1482,7 +1482,7 @@ export default class Document extends Node {
 	): NodeList<ISVGElementTagNameMap[K]>;
 
 	/**
-	 * Query CSS selector to find matching elments.
+	 * Query CSS selector to find matching elements.
 	 *
 	 * @param selector CSS selector.
 	 * @returns Matching elements.
@@ -1490,7 +1490,7 @@ export default class Document extends Node {
 	public querySelectorAll(selector: string): NodeList<Element>;
 
 	/**
-	 * Query CSS selector to find matching elments.
+	 * Query CSS selector to find matching elements.
 	 *
 	 * @param selector CSS selector.
 	 * @returns Matching elements.
diff --git a/packages/happy-dom/src/query-selector/QuerySelector.ts b/packages/happy-dom/src/query-selector/QuerySelector.ts
index 6209a0cee..1a4b38c2c 100644
--- a/packages/happy-dom/src/query-selector/QuerySelector.ts
+++ b/packages/happy-dom/src/query-selector/QuerySelector.ts
@@ -115,7 +115,11 @@ export default class QuerySelector {
 			}
 		}
 
-		const groups = SelectorParser.getSelectorGroups(selector, { scope: node });
+		const scope =
+			node[PropertySymbol.nodeType] === NodeTypeEnum.documentNode
+				? (<Document>node).documentElement
+				: node;
+		const groups = SelectorParser.getSelectorGroups(selector, { scope });
 		const items: Element[] = [];
 		const nodeList = new NodeList<Element>(PropertySymbol.illegalConstructor, items);
 		const matchesMap: Map<string, Element> = new Map();
@@ -251,7 +255,11 @@ export default class QuerySelector {
 
 		const matchesMap: Map<string, Element> = new Map();
 		const matchedPositions: string[] = [];
-		for (const items of SelectorParser.getSelectorGroups(selector, { scope: node })) {
+		const scope =
+			node[PropertySymbol.nodeType] === NodeTypeEnum.documentNode
+				? (<Document>node).documentElement
+				: node;
+		for (const items of SelectorParser.getSelectorGroups(selector, { scope })) {
 			const match =
 				node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode
 					? this.findFirst(<Element>node, [<Element>node], items, cachedItem)
@@ -350,9 +358,14 @@ export default class QuerySelector {
 			);
 		}
 
+		const scopeOrElement = options?.scope || element;
+		const scope =
+			scopeOrElement[PropertySymbol.nodeType] === NodeTypeEnum.documentNode
+				? (<Document>scopeOrElement).documentElement
+				: scopeOrElement;
 		for (const items of SelectorParser.getSelectorGroups(selector, {
 			...options,
-			scope: options?.scope || element
+			scope
 		})) {
 			const result = this.matchSelector(element, items.reverse(), cachedItem);
 
diff --git a/packages/happy-dom/src/query-selector/SelectorItem.ts b/packages/happy-dom/src/query-selector/SelectorItem.ts
index 16e5caeb0..426c6ba60 100644
--- a/packages/happy-dom/src/query-selector/SelectorItem.ts
+++ b/packages/happy-dom/src/query-selector/SelectorItem.ts
@@ -5,7 +5,6 @@ import SelectorCombinatorEnum from './SelectorCombinatorEnum.js';
 import ISelectorAttribute from './ISelectorAttribute.js';
 import ISelectorMatch from './ISelectorMatch.js';
 import ISelectorPseudo from './ISelectorPseudo.js';
-import Document from '../nodes/document/Document.js';
 import DocumentFragment from '../nodes/document-fragment/DocumentFragment.js';
 
 const SPACE_REGEXP = /\s+/;
@@ -14,7 +13,8 @@ const SPACE_REGEXP = /\s+/;
  * Selector item.
  */
 export default class SelectorItem {
-	public scope: Element | Document | DocumentFragment | null;
+	public root: Element | DocumentFragment | null;
+	public scope: Element | DocumentFragment | null;
 	public tagName: string | null;
 	public id: string | null;
 	public classNames: string[] | null;
@@ -39,7 +39,7 @@ export default class SelectorItem {
 	 * @param [options.ignoreErrors] Ignore errors.
 	 */
 	constructor(options?: {
-		scope?: Element | Document | DocumentFragment;
+		scope?: Element | DocumentFragment;
 		tagName?: string;
 		id?: string;
 		classNames?: string[];
@@ -49,6 +49,7 @@ export default class SelectorItem {
 		combinator?: SelectorCombinatorEnum;
 		ignoreErrors?: boolean;
 	}) {
+		this.root = options?.scope ? options.scope[PropertySymbol.ownerDocument].documentElement : null;
 		this.scope = options?.scope || null;
 		this.tagName = options?.tagName || null;
 		this.id = options?.id || null;
@@ -251,7 +252,7 @@ export default class SelectorItem {
 					? { priorityWeight: 10 }
 					: null;
 			case 'root':
-				return element[PropertySymbol.tagName] === 'HTML' ? { priorityWeight: 10 } : null;
+				return this.root && element === this.root ? { priorityWeight: 10 } : null;
 			case 'not':
 				for (const selectorItem of pseudo.selectorItems!) {
 					if (selectorItem.match(element)) {
@@ -385,7 +386,7 @@ export default class SelectorItem {
 					? { priorityWeight: 10 }
 					: null;
 			case 'scope':
-				return this.scope === element ? { priorityWeight: 10 } : null;
+				return this.scope && this.scope === element ? { priorityWeight: 10 } : null;
 			default:
 				return null;
 		}
diff --git a/packages/happy-dom/src/query-selector/SelectorParser.ts b/packages/happy-dom/src/query-selector/SelectorParser.ts
index 2592ffc23..6dcfbc9db 100644
--- a/packages/happy-dom/src/query-selector/SelectorParser.ts
+++ b/packages/happy-dom/src/query-selector/SelectorParser.ts
@@ -3,7 +3,6 @@ import SelectorCombinatorEnum from './SelectorCombinatorEnum.js';
 import DOMException from '../exception/DOMException.js';
 import ISelectorPseudo from './ISelectorPseudo.js';
 import Element from '../nodes/element/Element.js';
-import Document from '../nodes/document/Document.js';
 import DocumentFragment from '../nodes/document-fragment/DocumentFragment.js';
 
 /**
@@ -72,13 +71,16 @@ export default class SelectorParser {
 	 *
 	 * @param selector Selector.
 	 * @param options Options.
-	 * @param options.scope Scope.
+	 * @param [options.scope] Scope.
 	 * @param [options.ignoreErrors] Ignores errors.
 	 * @returns Selector item.
 	 */
 	public static getSelectorItem(
 		selector: string,
-		options?: { scope?: Element | Document | DocumentFragment; ignoreErrors?: boolean }
+		options?: {
+			scope?: Element | DocumentFragment;
+			ignoreErrors?: boolean;
+		}
 	): SelectorItem {
 		return this.getSelectorGroups(selector, options)[0][0];
 	}
@@ -88,13 +90,16 @@ export default class SelectorParser {
 	 *
 	 * @param selector Selector.
 	 * @param options Options.
-	 * @param options.scope Scope.
+	 * @param [options.scope] Scope.
 	 * @param [options.ignoreErrors] Ignores errors.
 	 * @returns Selector groups.
 	 */
 	public static getSelectorGroups(
 		selector: string,
-		options?: { scope?: Element | Document | DocumentFragment; ignoreErrors?: boolean }
+		options?: {
+			scope?: Element | DocumentFragment;
+			ignoreErrors?: boolean;
+		}
 	): Array<Array<SelectorItem>> {
 		selector = selector.trim();
 		const ignoreErrors = options?.ignoreErrors;
@@ -296,15 +301,18 @@ export default class SelectorParser {
 	 *
 	 * @param name Pseudo name.
 	 * @param args Pseudo arguments.
-	 * @param options Options.
-	 * @param options.scope Scope.
+	 * @param [options] Options.
+	 * @param [options.scope] Scope.
 	 * @param [options.ignoreErrors] Ignores errors.
 	 * @returns Pseudo.
 	 */
 	private static getPseudo(
 		name: string,
 		args: string | null | undefined,
-		options?: { scope?: Element | Document | DocumentFragment; ignoreErrors?: boolean }
+		options?: {
+			scope?: Element | DocumentFragment;
+			ignoreErrors?: boolean;
+		}
 	): ISelectorPseudo {
 		const lowerName = name.toLowerCase();