Repository: capricorn86/happy-dom
Pull request #885 (2023-05-02 14:47:39)

#883@patch: Fixes issue with attribute query selectors not using apos…
…trophe for the value.


Patch:
diff --git a/packages/happy-dom/src/query-selector/QuerySelector.ts b/packages/happy-dom/src/query-selector/QuerySelector.ts
index e6f796cf..64b777e9 100644
--- a/packages/happy-dom/src/query-selector/QuerySelector.ts
+++ b/packages/happy-dom/src/query-selector/QuerySelector.ts
@@ -97,6 +97,12 @@ export default class QuerySelector {
 	 * @returns Result.
 	 */
 	public static match(element: IElement, selector: string): ISelectorMatch | null {
+		if (selector === '*') {
+			return {
+				priorityWeight: 1
+			};
+		}
+
 		for (const items of SelectorParser.getSelectorGroups(selector)) {
 			const result = this.matchSelector(element, element, items.reverse());
 
diff --git a/packages/happy-dom/src/query-selector/SelectorItem.ts b/packages/happy-dom/src/query-selector/SelectorItem.ts
index dff7da07..673e4c57 100644
--- a/packages/happy-dom/src/query-selector/SelectorItem.ts
+++ b/packages/happy-dom/src/query-selector/SelectorItem.ts
@@ -12,7 +12,6 @@ import ISelectorPseudo from './ISelectorPseudo';
  * Selector item.
  */
 export default class SelectorItem {
-	public all: string | null;
 	public tagName: string | null;
 	public id: string | null;
 	public classNames: string[] | null;
@@ -25,7 +24,6 @@ export default class SelectorItem {
 	 *
 	 * @param [options] Options.
 	 * @param [options.combinator] Combinator.
-	 * @param [options.all] All.
 	 * @param [options.tagName] Tag name.
 	 * @param [options.id] ID.
 	 * @param [options.classNames] Class names.
@@ -33,7 +31,6 @@ export default class SelectorItem {
 	 * @param [options.pseudos] Pseudos.
 	 */
 	constructor(options?: {
-		all?: string;
 		tagName?: string;
 		id?: string;
 		classNames?: string[];
@@ -41,7 +38,6 @@ export default class SelectorItem {
 		pseudos?: ISelectorPseudo[];
 		combinator?: SelectorCombinatorEnum;
 	}) {
-		this.all = options?.all || null;
 		this.tagName = options?.tagName || null;
 		this.id = options?.id || null;
 		this.classNames = options?.classNames || null;
@@ -61,7 +57,7 @@ export default class SelectorItem {
 
 		// Tag name match
 		if (this.tagName) {
-			if (this.tagName !== element.tagName) {
+			if (this.tagName !== '*' && this.tagName !== element.tagName) {
 				return null;
 			}
 			priorityWeight += 1;
@@ -354,7 +350,7 @@ export default class SelectorItem {
 	 * @returns Selector string.
 	 */
 	private getSelectorString(): string {
-		return `${this.all || ''}${this.tagName || ''}${this.id ? `#${this.id}` : ''}${
+		return `${this.tagName || ''}${this.id ? `#${this.id}` : ''}${
 			this.classNames ? `.${this.classNames.join('.')}` : ''
 		}${
 			this.attributes
diff --git a/packages/happy-dom/src/query-selector/SelectorParser.ts b/packages/happy-dom/src/query-selector/SelectorParser.ts
index a38f63cc..ca1843fd 100644
--- a/packages/happy-dom/src/query-selector/SelectorParser.ts
+++ b/packages/happy-dom/src/query-selector/SelectorParser.ts
@@ -10,16 +10,19 @@ import DOMException from '../exception/DOMException';
  * Group 3: ID (e.g. "#id")
  * Group 4: Class (e.g. ".class")
  * Group 5: Attribute name when no value (e.g. "attr1")
- * Group 6: Attribute name when there is a value (e.g. "attr1")
- * Group 7: Attribute operator (e.g. "~")
- * Group 8: Attribute value (e.g. "value1")
- * Group 9: Pseudo name when arguments (e.g. "nth-child")
- * Group 10: Arguments of pseudo (e.g. "2n + 1")
- * Group 11: Pseudo name when no arguments (e.g. "empty")
- * Group 12: Combinator.
+ * Group 6: Attribute name when there is a value using apostrophe (e.g. "attr1")
+ * Group 7: Attribute operator when using apostrophe (e.g. "~")
+ * Group 8: Attribute value when using apostrophe (e.g. "value1")
+ * Group 9: Attribute name when threre is a value not using apostrophe (e.g. "attr1")
+ * Group 10: Attribute operator when not using apostrophe (e.g. "~")
+ * Group 11: Attribute value when notusing apostrophe (e.g. "value1")
+ * Group 12: Pseudo name when arguments (e.g. "nth-child")
+ * Group 13: Arguments of pseudo (e.g. "2n + 1")
+ * Group 14: Pseudo name when no arguments (e.g. "empty")
+ * Group 15: Combinator.
  */
 const SELECTOR_REGEXP =
-	/(\*)|([a-zA-Z0-9-]+)|#((?:[a-zA-Z0-9-_]|\\.)+)|\.((?:[a-zA-Z0-9-_]|\\.)+)|\[([a-zA-Z0-9-_]+)\]|\[([a-zA-Z0-9-_]+)([~|^$*]{0,1}) *= *["']{0,1}([^"']*)["']{0,1}\]|:([a-zA-Z-]+) *\(([^)]+)\)|:([a-zA-Z-]+)|([ ,+>]*)/g;
+	/(\*)|([a-zA-Z0-9-]+)|#((?:[a-zA-Z0-9-_]|\\.)+)|\.((?:[a-zA-Z0-9-_]|\\.)+)|\[([a-zA-Z0-9-_]+)\]|\[([a-zA-Z0-9-_]+)([~|^$*]{0,1}) *= *["']{1}([^"']*)["']{1}\]|\[([a-zA-Z0-9-_]+)([~|^$*]{0,1}) *= *([^\]]*)\]|:([a-zA-Z-]+) *\(([^)]+)\)|:([a-zA-Z-]+)|([ ,+>]*)/g;
 
 /**
  * Escaped Character RegExp.
@@ -56,6 +59,10 @@ export default class SelectorParser {
 	 * @returns Selector groups.
 	 */
 	public static getSelectorGroups(selector: string): Array<Array<SelectorItem>> {
+		if (selector === '*') {
+			return [[new SelectorItem({ tagName: '*' })]];
+		}
+
 		const simpleMatch = selector.match(SIMPLE_SELECTOR_REGEXP);
 
 		if (simpleMatch) {
@@ -82,7 +89,7 @@ export default class SelectorParser {
 				isValid = true;
 
 				if (match[1]) {
-					currentSelectorItem.all = '*';
+					currentSelectorItem.tagName = '*';
 				} else if (match[2]) {
 					currentSelectorItem.tagName = match[2].toUpperCase();
 				} else if (match[3]) {
@@ -104,20 +111,27 @@ export default class SelectorParser {
 						operator: match[7] || null,
 						value: match[8]
 					});
-				} else if (match[9] && match[10]) {
+				} else if (match[9] && match[11] !== undefined) {
+					currentSelectorItem.attributes = currentSelectorItem.attributes || [];
+					currentSelectorItem.attributes.push({
+						name: match[9].toLowerCase(),
+						operator: match[10] || null,
+						value: match[11]
+					});
+				} else if (match[12] && match[13]) {
 					currentSelectorItem.pseudos = currentSelectorItem.pseudos || [];
 					currentSelectorItem.pseudos.push({
-						name: match[9].toLowerCase(),
-						arguments: match[10]
+						name: match[12].toLowerCase(),
+						arguments: match[13]
 					});
-				} else if (match[11]) {
+				} else if (match[14]) {
 					currentSelectorItem.pseudos = currentSelectorItem.pseudos || [];
 					currentSelectorItem.pseudos.push({
-						name: match[11].toLowerCase(),
+						name: match[14].toLowerCase(),
 						arguments: null
 					});
-				} else if (match[12]) {
-					switch (match[12].trim()) {
+				} else if (match[15]) {
+					switch (match[15].trim()) {
 						case ',':
 							currentSelectorItem = new SelectorItem({
 								combinator: SelectorCombinatorEnum.descendant


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 3090c4b7..5affbf8d 100644
--- a/packages/happy-dom/test/query-selector/QuerySelector.test.ts
+++ b/packages/happy-dom/test/query-selector/QuerySelector.test.ts
@@ -369,6 +369,20 @@ describe('QuerySelector', () => {
 			expect(elements[2] === container.children[0].children[1].children[2]).toBe(true);
 		});
 
+		it('Returns all elements with tag name and matching attributes using Testing Library query "[type=submit], input[type=button], input[type=reset]".', () => {
+			const container = document.createElement('div');
+
+			container.innerHTML = `<input type="submit"></input><input type="reset"></input>`;
+
+			const elements = container.querySelectorAll(
+				'input[type=submit], input[type=button], input[type=reset]'
+			);
+
+			expect(elements.length).toBe(2);
+			expect(elements[0] === container.children[0]).toBe(true);
+			expect(elements[1] === container.children[1]).toBe(true);
+		});
+
 		it('Returns all elements with tag name and multiple matching attributes using "span[attr1="value1"][attr2="word1 word2"]".', () => {
 			const container = document.createElement('div');
 			container.innerHTML = QuerySelectorHTML;
diff --git a/packages/jest-environment/test/testing-library/TestingLibrary.test.tsx b/packages/jest-environment/test/testing-library/TestingLibrary.test.tsx
index a1907421..c709c3a7 100644
--- a/packages/jest-environment/test/testing-library/TestingLibrary.test.tsx
+++ b/packages/jest-environment/test/testing-library/TestingLibrary.test.tsx
@@ -43,4 +43,17 @@ describe('TestingLibrary', () => {
 
 		expect(changeHandler).toHaveBeenCalledTimes(1);
 	});
+
+	it('Finds elements using "screen.getByText()".', async () => {
+		const user = userEvent.setup();
+		const clickHandler = jest.fn();
+
+		render(<input type="submit" value="Submit Button" onClick={clickHandler} />);
+
+		const element = screen.getByText('Submit Button');
+
+		await user.click(element);
+
+		expect(clickHandler).toHaveBeenCalledTimes(1);
+	});
 });