Repository: capricorn86/happy-dom (ts)

Issue:
Value of attribute query selector can not contain any . (point) character
**Describe the bug**
Using attributes like `name="offer.email"` are not queryable with happy-dom.

**To Reproduce**
Clone repo and adjust following files and run tests.

QuerySelectorHTML.ts:
```
export default `
	<div class="class1 class2">
		<!-- Comment 1 !-->
		<h1>Heading1</h1>
		<!-- Comment 2 !-->
		<div class="class1 class2">
			<span class="class1 class2" attr1="value1" attr2="word1 word2" attr3="bracket[]bracket" attr4="word1.word2" type="hidden">Span1</span>
			<span class="class1 class2" attr1="value1">Span2</span>
		</div>
	</div>
	<div>
		<!-- Comment 1 !-->
		<h1>Heading1</h1>
		<!-- Comment 2 !-->
	</div>
`.trim();
```

QuerySelector.test.ts, line 231:
```
		it('Returns all elements with matching attribute containing dot char using "[attr4="word1.word2"]".', () => {
			const container = document.createElement('div');
			container.innerHTML = QuerySelectorHTML;
			const elements = container.querySelectorAll('[attr4="word1.word2"]');

			expect(elements.length).toBe(1);
			expect(elements[0]).toBe(container.children[0].children[1].children[0]);
		});
```

**Expected behavior**
Element should be queryable by attribute with point character in it.

**Device:**
 - Version 8.2.3


Pull request:
#715@patch: Attribute value can contains point.
fix #715 

Patch:
diff --git a/packages/happy-dom/src/query-selector/SelectorItem.ts b/packages/happy-dom/src/query-selector/SelectorItem.ts
index bb1a968a..cca4cb5c 100644
--- a/packages/happy-dom/src/query-selector/SelectorItem.ts
+++ b/packages/happy-dom/src/query-selector/SelectorItem.ts
@@ -41,7 +41,8 @@ export default class SelectorItem {
 		this.isAttribute = !this.isAll && baseSelector.includes('[');
 		// If baseSelector !== selector then some psuedo selector was replaced above
 		this.isPseudo = !this.isAll && baseSelector !== selector;
-		this.isClass = !this.isAll && new RegExp(CLASS_REGEXP, 'g').test(baseSelector);
+		this.isClass =
+			!this.isAll && new RegExp(CLASS_REGEXP, 'g').test(baseSelector) && !this.isAttribute;
 		this.tagName = !this.isAll ? baseSelector.match(TAG_NAME_REGEXP) : null;
 		this.tagName = this.tagName ? this.tagName[0].toUpperCase() : null;
 		this.isTagName = this.tagName !== null;


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 b9be1b0d..0adf0de4 100644
--- a/packages/happy-dom/test/query-selector/QuerySelector.test.ts
+++ b/packages/happy-dom/test/query-selector/QuerySelector.test.ts
@@ -19,9 +19,10 @@ describe('QuerySelector', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('span');
-			expect(elements.length).toBe(2);
+			expect(elements.length).toBe(3);
 			expect(elements[0]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[2]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns a NodeList with the method item().', () => {
@@ -44,11 +45,12 @@ describe('QuerySelector', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('.class1');
-			expect(elements.length).toBe(4);
+			expect(elements.length).toBe(5);
 			expect(elements[0]).toBe(container.children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1]);
 			expect(elements[2]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[3]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[4]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements with class name "before:after".', () => {
@@ -106,39 +108,43 @@ describe('QuerySelector', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('.class1.class2');
-			expect(elements.length).toBe(4);
+			expect(elements.length).toBe(5);
 			expect(elements[0]).toBe(container.children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1]);
 			expect(elements[2]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[3]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[4]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements matching ".class1 > .class1 > *".', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('.class1 > .class1 > *');
-			expect(elements.length).toBe(2);
+			expect(elements.length).toBe(3);
 			expect(elements[0]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[2]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements matching "div > div > span".', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('div > div > span');
-			expect(elements.length).toBe(2);
+			expect(elements.length).toBe(3);
 			expect(elements[0]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[2]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements matching "div > div > .class1.class2".', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('div > div > .class1.class2');
-			expect(elements.length).toBe(3);
+			expect(elements.length).toBe(4);
 			expect(elements[0]).toBe(container.children[0].children[1]);
 			expect(elements[1]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[2]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[3]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements with tag name and class "span.class1".', () => {
@@ -146,9 +152,10 @@ describe('QuerySelector', () => {
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('span.class1');
 
-			expect(elements.length).toBe(2);
+			expect(elements.length).toBe(3);
 			expect(elements[0]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[2]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements with matching attributes using "[attr1="value1"]".', () => {
@@ -161,6 +168,15 @@ describe('QuerySelector', () => {
 			expect(elements[1]).toBe(container.children[0].children[1].children[1]);
 		});
 
+		it('Returns all elements with matching attributes using "[attr1="word1.word2"]".', () => {
+			const container = document.createElement('div');
+			container.innerHTML = QuerySelectorHTML;
+			const elements = container.querySelectorAll('[attr1="word1.word2"]');
+
+			expect(elements.length).toBe(1);
+			expect(elements[0]).toBe(container.children[0].children[1].children[2]);
+		});
+
 		it('Returns all elements with multiple matching attributes using "[attr1="value1"][attr2="word1 word2"]".', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;
@@ -205,9 +221,10 @@ describe('QuerySelector', () => {
 			container.innerHTML = QuerySelectorHTML.replace(/ attr1/gm, '_attr1');
 			const elements = container.querySelectorAll('span[_attr1]');
 
-			expect(elements.length).toBe(2);
+			expect(elements.length).toBe(3);
 			expect(elements[0]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[2]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements with tag name and multiple matching attributes using "span[attr1="value1"][attr2="word1 word2"]".', () => {
@@ -246,11 +263,12 @@ describe('QuerySelector', () => {
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('[class~="class2"]');
 
-			expect(elements.length).toBe(4);
+			expect(elements.length).toBe(5);
 			expect(elements[0]).toBe(container.children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1]);
 			expect(elements[2]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[3]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[4]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements with an attribute value starting with the specified word using "[class|="class1"]".', () => {
@@ -258,11 +276,12 @@ describe('QuerySelector', () => {
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('[class|="class1"]');
 
-			expect(elements.length).toBe(4);
+			expect(elements.length).toBe(5);
 			expect(elements[0]).toBe(container.children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1]);
 			expect(elements[2]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[3]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[4]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements with an attribute value that begins with a specified value using "[class^="cl"]".', () => {
@@ -270,11 +289,12 @@ describe('QuerySelector', () => {
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('[class^="cl"]');
 
-			expect(elements.length).toBe(4);
+			expect(elements.length).toBe(5);
 			expect(elements[0]).toBe(container.children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1]);
 			expect(elements[2]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[3]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[4]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements with an attribute value that ends with a specified value using "[class$="ss2"]".', () => {
@@ -282,11 +302,12 @@ describe('QuerySelector', () => {
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('[class$="ss2"]');
 
-			expect(elements.length).toBe(4);
+			expect(elements.length).toBe(5);
 			expect(elements[0]).toBe(container.children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1]);
 			expect(elements[2]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[3]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[4]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements with an attribute value that contains a specified value using "[class*="s1 cl"]".', () => {
@@ -294,11 +315,12 @@ describe('QuerySelector', () => {
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('[class*="s1 cl"]');
 
-			expect(elements.length).toBe(4);
+			expect(elements.length).toBe(5);
 			expect(elements[0]).toBe(container.children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1]);
 			expect(elements[2]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[3]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[4]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements with an attribute value that contains a specified value using "[class*="s1 cl"]" or matches exactly a value using "[attr1="value1"]".', () => {
@@ -306,25 +328,28 @@ describe('QuerySelector', () => {
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('[class*="s1 cl"], [attr1="value1"]');
 
-			expect(elements.length).toBe(4);
+			expect(elements.length).toBe(5);
 			expect(elements[0]).toBe(container.children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1]);
 			expect(elements[2]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[3]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[4]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements with an attribute value that contains a specified value using "[class*="s1 cl"]" or has the tag "b".', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('[class*="s1 cl"], h1');
-
-			expect(elements.length).toBe(6);
+			const children = container.children;
+			expect(children.length).toBe(2);
+			expect(elements.length).toBe(7);
 			expect(elements[0]).toBe(container.children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1]);
 			expect(elements[2]).toBe(container.children[0].children[1].children[0]);
 			expect(elements[3]).toBe(container.children[0].children[1].children[1]);
-			expect(elements[4]).toBe(container.children[0].children[0]);
-			expect(elements[5]).toBe(container.children[1].children[0]);
+			expect(elements[4]).toBe(container.children[0].children[1].children[2]);
+			expect(elements[5]).toBe(container.children[0].children[0]);
+			expect(elements[6]).toBe(container.children[1].children[0]);
 		});
 
 		it('Returns all span elements matching ":first-child".', () => {
@@ -355,7 +380,7 @@ describe('QuerySelector', () => {
 
 			expect(elements.length).toBe(4);
 			expect(elements[0]).toBe(container.children[0].children[1]);
-			expect(elements[1]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[1]).toBe(container.children[0].children[1].children[2]);
 			expect(elements[2]).toBe(container.children[1]);
 			expect(elements[3]).toBe(container.children[1].children[0]);
 		});
@@ -366,7 +391,7 @@ describe('QuerySelector', () => {
 			const elements = container.querySelectorAll('span:last-child');
 
 			expect(elements.length).toBe(1);
-			expect(elements[0]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[0]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all span elements matching ":only-child".', () => {
@@ -408,7 +433,7 @@ describe('QuerySelector', () => {
 			expect(elements.length).toBe(5);
 			expect(elements[0]).toBe(container.children[0].children[0]);
 			expect(elements[1]).toBe(container.children[0].children[1]);
-			expect(elements[2]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[2]).toBe(container.children[0].children[1].children[2]);
 			expect(elements[3]).toBe(container.children[1]);
 			expect(elements[4]).toBe(container.children[1].children[0]);
 		});
@@ -418,8 +443,9 @@ describe('QuerySelector', () => {
 			container.innerHTML = QuerySelectorHTML;
 			const elements = container.querySelectorAll('span:not([type=hidden])');
 
-			expect(elements.length).toBe(1);
+			expect(elements.length).toBe(2);
 			expect(elements[0]).toBe(container.children[0].children[1].children[1]);
+			expect(elements[1]).toBe(container.children[0].children[1].children[2]);
 		});
 
 		it('Returns all elements matching ".foo:not(.bar)".', () => {
diff --git a/packages/happy-dom/test/query-selector/data/QuerySelectorHTML.ts b/packages/happy-dom/test/query-selector/data/QuerySelectorHTML.ts
index 8baa25de..dfb5663e 100644
--- a/packages/happy-dom/test/query-selector/data/QuerySelectorHTML.ts
+++ b/packages/happy-dom/test/query-selector/data/QuerySelectorHTML.ts
@@ -6,6 +6,7 @@ export default `
 		<div class="class1 class2">
 			<span class="class1 class2" attr1="value1" attr2="word1 word2" attr3="bracket[]bracket" type="hidden">Span1</span>
 			<span class="class1 class2" attr1="value1">Span2</span>
+			<span class="class1 class2" attr1="word1.word2">Span3</span>
 		</div>
 	</div>
 	<div>