Repository: capricorn86/happy-dom
Pull request #397 (2022-03-10 11:53:28)

#395@patch: Fixes issue with CSS query selectors when selecting "tagN…
…ame.class". The bugfix in v2.43.1 caused the bug.


Patch:
diff --git a/packages/happy-dom/src/query-selector/SelectorItem.ts b/packages/happy-dom/src/query-selector/SelectorItem.ts
index 09a2736d..b3ab7010 100644
--- a/packages/happy-dom/src/query-selector/SelectorItem.ts
+++ b/packages/happy-dom/src/query-selector/SelectorItem.ts
@@ -27,16 +27,18 @@ export default class SelectorItem {
 	 * @param selector Selector.
 	 */
 	constructor(selector: string) {
-		this.isAll = selector === '*';
+		const [baseSelector, psuedoSelector] = selector.split(':');
+
+		this.isAll = baseSelector === '*';
 		this.isID = !this.isAll ? selector.startsWith('#') : false;
-		this.isAttribute = !this.isAll && !this.isID && selector.includes('[');
-		this.isPseudo = !this.isAll && !this.isID && selector.includes(':');
-		this.isClass = !this.isAll && !this.isID && new RegExp(CLASS_REGEXP, 'g').test(selector);
-		this.tagName = !this.isAll && !this.isID ? selector.match(TAG_NAME_REGEXP) : null;
+		this.isAttribute = !this.isAll && !this.isID && baseSelector.includes('[');
+		this.isPseudo = !this.isAll && !this.isID && psuedoSelector !== undefined;
+		this.isClass = !this.isAll && !this.isID && new RegExp(CLASS_REGEXP, 'g').test(baseSelector);
+		this.tagName = !this.isAll && !this.isID ? baseSelector.match(TAG_NAME_REGEXP) : null;
 		this.tagName = this.tagName ? this.tagName[0].toUpperCase() : null;
 		this.isTagName = this.tagName !== null;
 		this.selector = selector;
-		this.id = !this.isAll && this.isID ? this.selector.replace('#', '') : null;
+		this.id = !this.isAll && this.isID ? baseSelector.replace('#', '') : null;
 	}
 
 	/**
@@ -257,14 +259,12 @@ export default class SelectorItem {
 	private matchesClass(element: Element, selector: string): boolean {
 		const regexp = new RegExp(CLASS_REGEXP, 'g');
 		const classList = element.className.split(' ');
-		let previousIndex = 0;
 		let match: RegExpMatchArray;
 
-		while ((match = regexp.exec(selector)) && match.index === previousIndex) {
+		while ((match = regexp.exec(selector.split(':')[0]))) {
 			if (!classList.includes(match[1])) {
 				return false;
 			}
-			previousIndex = match.index + match[0].length - 1;
 		}
 
 		return true;


Test patch:
diff --git a/packages/happy-dom/test/query-selector/QuerySelector.test.ts b/packages/happy-dom/test/query-selector/QuerySelector.test.ts
index ff8a1622..ee324ff0 100644
--- a/packages/happy-dom/test/query-selector/QuerySelector.test.ts
+++ b/packages/happy-dom/test/query-selector/QuerySelector.test.ts
@@ -90,6 +90,16 @@ describe('QuerySelector', () => {
 			expect(elements[2]).toBe(container.children[0].children[1].children[1]);
 		});
 
+		it('Returns all elements with tag name and class "span.class1".', () => {
+			const container = document.createElement('div');
+			container.innerHTML = QuerySelectorHTML;
+			const elements = container.querySelectorAll('span.class1');
+
+			expect(elements.length).toBe(2);
+			expect(elements[0]).toBe(container.children[0].children[1].children[0]);
+			expect(elements[1]).toBe(container.children[0].children[1].children[1]);
+		});
+
 		it('Returns all elements with matching attributes using "[attr1="value1"]".', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;
@@ -550,6 +560,21 @@ describe('QuerySelector', () => {
 			expect(div1.querySelector('span.spanClass')).toBe(span);
 		});
 
+		it('Returns span with a specific class name and tag name matching "custom-element.class1".', () => {
+			const div = document.createElement('div');
+			const customElement1 = document.createElement('custom-element');
+			const customElement2 = document.createElement('custom-element');
+			const customElement3 = document.createElement('custom-element');
+			customElement1.className = 'class1';
+			customElement2.className = 'class2';
+			customElement3.className = 'class3';
+			div.appendChild(customElement1);
+			div.appendChild(customElement2);
+			div.appendChild(customElement3);
+
+			expect(div.querySelector('custom-element.class2') === customElement2).toBe(true);
+		});
+
 		it('Returns span with a specific tag name and attribute matching "span[attr1="value1"]".', () => {
 			const div1 = document.createElement('div');
 			const div2 = document.createElement('div');