Repository: capricorn86/happy-dom
Pull request #838 (2023-04-04 16:35:06)

#835@patch: Fixes issue with parsing a form containing fields (other …
…than checkbox and radio) using the same name in FormData.


Patch:
diff --git a/packages/happy-dom/src/form-data/FormData.ts b/packages/happy-dom/src/form-data/FormData.ts
index 6f8f28b9..62a370a5 100644
--- a/packages/happy-dom/src/form-data/FormData.ts
+++ b/packages/happy-dom/src/form-data/FormData.ts
@@ -3,9 +3,7 @@ import File from '../file/File';
 import IHTMLInputElement from '../nodes/html-input-element/IHTMLInputElement';
 import IHTMLFormElement from '../nodes/html-form-element/IHTMLFormElement';
 import HTMLFormControlsCollection from '../nodes/html-form-element/HTMLFormControlsCollection';
-import IHTMLTextAreaElement from '../nodes/html-text-area-element/IHTMLTextAreaElement';
-import IHTMLSelectElement from '../nodes/html-select-element/IHTMLSelectElement';
-import IHTMLButtonElement from '../nodes/html-button-element/IHTMLButtonElement';
+import RadioNodeList from '../nodes/html-form-element/RadioNodeList';
 
 type FormDataEntry = {
 	name: string;
@@ -30,32 +28,35 @@ export default class FormData implements Iterable<[string, string | File]> {
 	constructor(form?: IHTMLFormElement) {
 		if (form) {
 			for (const name of Object.keys((<HTMLFormControlsCollection>form.elements)._namedItems)) {
-				const radioNodeList = (<HTMLFormControlsCollection>form.elements)._namedItems[name];
-				let element:
-					| IHTMLInputElement
-					| IHTMLTextAreaElement
-					| IHTMLSelectElement
-					| IHTMLButtonElement = radioNodeList[0];
-
-				if (radioNodeList.length > 1) {
-					for (const radioNode of radioNodeList) {
-						if (radioNode.checked) {
-							element = radioNode;
+				let radioNodeList = (<HTMLFormControlsCollection>form.elements)._namedItems[name];
+
+				if (
+					radioNodeList[0].tagName === 'INPUT' &&
+					(radioNodeList[0].type === 'checkbox' || radioNodeList[0].type === 'radio')
+				) {
+					const newRadioNodeList = new RadioNodeList();
+					for (const node of radioNodeList) {
+						if (node.checked) {
+							newRadioNodeList.push(node);
+							break;
 						}
 					}
+					radioNodeList = newRadioNodeList;
 				}
 
-				if (element.name && SUBMITTABLE_ELEMENTS.includes(element.tagName)) {
-					if (element.tagName === 'INPUT' && element.type === 'file') {
-						if ((<IHTMLInputElement>element).files.length === 0) {
-							this.append(element.name, new File([], '', { type: 'application/octet-stream' }));
-						} else {
-							for (const file of (<IHTMLInputElement>element).files) {
-								this.append(element.name, file);
+				for (const node of radioNodeList) {
+					if (node.name && SUBMITTABLE_ELEMENTS.includes(node.tagName)) {
+						if (node.tagName === 'INPUT' && node.type === 'file') {
+							if ((<IHTMLInputElement>node).files.length === 0) {
+								this.append(node.name, new File([], '', { type: 'application/octet-stream' }));
+							} else {
+								for (const file of (<IHTMLInputElement>node).files) {
+									this.append(node.name, file);
+								}
 							}
+						} else {
+							this.append(node.name, node.value);
 						}
-					} else {
-						this.append(element.name, element.value);
 					}
 				}
 			}


Test patch:
diff --git a/packages/happy-dom/test/form-data/FormData.test.ts b/packages/happy-dom/test/form-data/FormData.test.ts
index 0c49926e..5460a2e0 100644
--- a/packages/happy-dom/test/form-data/FormData.test.ts
+++ b/packages/happy-dom/test/form-data/FormData.test.ts
@@ -19,6 +19,8 @@ describe('FormData', () => {
 			const form = <IHTMLFormElement>document.createElement('form');
 			const file = new File([Buffer.from('fileContent')], 'file.txt', { type: 'text/plain' });
 			const textInput = <IHTMLInputElement>document.createElement('input');
+			const hiddenInput = <IHTMLInputElement>document.createElement('input');
+			const hiddenInput2 = <IHTMLInputElement>document.createElement('input');
 			const fileInput = <IHTMLInputElement>document.createElement('input');
 			const radioInput1 = <IHTMLInputElement>document.createElement('input');
 			const radioInput2 = <IHTMLInputElement>document.createElement('input');
@@ -29,6 +31,14 @@ describe('FormData', () => {
 			textInput.name = 'textInput';
 			textInput.value = 'text value';
 
+			hiddenInput.type = 'hidden';
+			hiddenInput.name = 'hiddenInput';
+			hiddenInput.value = 'hidden value 1';
+
+			hiddenInput2.type = 'hidden';
+			hiddenInput2.name = 'hiddenInput';
+			hiddenInput2.value = 'hidden value 2';
+
 			fileInput.type = 'file';
 			fileInput.name = 'fileInput';
 			fileInput.files.push(file);
@@ -53,6 +63,8 @@ describe('FormData', () => {
 			checkboxInput2.checked = true;
 
 			form.appendChild(textInput);
+			form.appendChild(hiddenInput);
+			form.appendChild(hiddenInput2);
 			form.appendChild(fileInput);
 			form.appendChild(radioInput1);
 			form.appendChild(radioInput2);
@@ -62,8 +74,12 @@ describe('FormData', () => {
 			const formData = new window.FormData(form);
 
 			expect(formData.get('textInput')).toBe('text value');
+			expect(formData.get('hiddenInput')).toBe('hidden value 1');
 			expect(formData.get('fileInput')).toBe(file);
 			expect(formData.get('radioInput')).toBe('radio value 2');
+			expect(formData.get('checkboxInput')).toBe('checkbox value 2');
+			expect(formData.getAll('hiddenInput')).toEqual(['hidden value 1', 'hidden value 2']);
+			expect(formData.getAll('radioInput')).toEqual(['radio value 2']);
 			expect(formData.getAll('checkboxInput')).toEqual(['checkbox value 2']);
 		});
 	});