From 6e9f14e09dbccfabeeeed00f4056666034e709a0 Mon Sep 17 00:00:00 2001 From: karpiuMG Date: Sat, 1 Mar 2025 23:00:07 +0100 Subject: [PATCH] feat: [#1427] Support submitter in formData constructor and exclude submit buttons from formData by default --- packages/happy-dom/src/form-data/FormData.ts | 45 +++++++++++++--- .../happy-dom/test/form-data/FormData.test.ts | 51 ++++++++++++++++++- 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/packages/happy-dom/src/form-data/FormData.ts b/packages/happy-dom/src/form-data/FormData.ts index b53604b0e..6f76b14c8 100644 --- a/packages/happy-dom/src/form-data/FormData.ts +++ b/packages/happy-dom/src/form-data/FormData.ts @@ -4,6 +4,9 @@ import File from '../file/File.js'; import HTMLInputElement from '../nodes/html-input-element/HTMLInputElement.js'; import HTMLFormElement from '../nodes/html-form-element/HTMLFormElement.js'; import BrowserWindow from '../window/BrowserWindow.js'; +import HTMLElement from '../nodes/html-element/HTMLElement.js'; +import DOMException from '../exception/DOMException.js'; +import HTMLButtonElement from '../nodes/html-button-element/HTMLButtonElement.js'; type FormDataEntry = { name: string; @@ -25,12 +28,45 @@ export default class FormData implements Iterable<[string, string | File]> { * Constructor. * * @param [form] Form. + * @param [submitter] A submit button that is a member of the form. */ - constructor(form?: HTMLFormElement) { + constructor(form?: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement) { if (!form) { return; } + if (submitter && submitter instanceof HTMLElement) { + const isInTheForm = submitter.form === form; + const formId = form.getAttribute('id'); + const submitterFormAttr = submitter.getAttribute('form'); + const isRefferingToTheForm = formId && submitterFormAttr === formId; + + if (!isInTheForm && !isRefferingToTheForm) { + throw new DOMException( + "Failed to construct 'FormData': The specified element is not owned by this form element.", + 'NotFoundError' + ); + } + + const isSubmitButton = + submitter[PropertySymbol.tagName] === 'BUTTON' && submitter.type === 'submit'; + const isSubmitInput = + submitter[PropertySymbol.tagName] === 'INPUT' && + ['submit', 'image'].includes(submitter.type); + + if (!isSubmitButton && !isSubmitInput) { + throw new TypeError( + "Failed to construct 'FormData': The specified element is not a submit button." + ); + } + + const submitterName = submitter.name; + if (submitterName) { + const submitterValue = submitter.value; + this.append(submitterName, submitterValue); + } + } + const items = form[PropertySymbol.getFormControlItems](); for (const item of items) { @@ -56,6 +92,8 @@ export default class FormData implements Iterable<[string, string | File]> { } break; case 'submit': + case 'image': + break; case 'reset': case 'button': if ((item).value) { @@ -67,11 +105,6 @@ export default class FormData implements Iterable<[string, string | File]> { break; } break; - case 'BUTTON': - if ((item).value) { - this.append(name, (item).value); - } - break; case 'TEXTAREA': case 'SELECT': this.append(name, (item).value); diff --git a/packages/happy-dom/test/form-data/FormData.test.ts b/packages/happy-dom/test/form-data/FormData.test.ts index 84b4ba349..66a2b1210 100644 --- a/packages/happy-dom/test/form-data/FormData.test.ts +++ b/packages/happy-dom/test/form-data/FormData.test.ts @@ -18,6 +18,30 @@ describe('FormData', () => { }); describe('constructor', () => { + it('throws TypeError when an invalid submitter type is provided', () => { + const form = document.createElement('form'); + const input = document.createElement('input'); + input.type = 'text'; + input.name = 'test'; + form.appendChild(input); + + expect(() => new window.FormData(form, input)).toThrow( + "Failed to construct 'FormData': The specified element is not a submit button." + ); + }); + + it('throws NotFoundError when submitter is not owned by the form', () => { + const form = document.createElement('form'); + const button = document.createElement('button'); + button.type = 'submit'; + button.name = 'submit-btn'; + + // Not appending the button to the form + expect(() => new window.FormData(form, button)).toThrow( + "Failed to construct 'FormData': The specified element is not owned by this form element." + ); + }); + it('Supports sending in an HTMLFormElement to the contructor.', () => { const form = document.createElement('form'); const file = new File([Buffer.from('fileContent')], 'file.txt', { type: 'text/plain' }); @@ -106,8 +130,31 @@ describe('FormData', () => { expect(formData.getAll('checkboxInput')).toEqual(['checkbox value 2']); expect(formData.get('button1')).toBe(null); expect(formData.get('button2')).toBe(null); - expect(formData.get('button3')).toBe('button3'); - expect(formData.get('button4')).toBe('button4'); + expect(formData.get('button3')).toBe(null); + expect(formData.get('button4')).toBe(null); + }); + + it('Only includes button values when they are passed in as submitter', () => { + const form = document.createElement('form'); + const input = document.createElement('input'); + const button = document.createElement('button'); + + input.name = 'input'; + input.value = 'testing'; + + button.name = 'button'; + button.value = 'buttonValue'; + + form.appendChild(input); + form.appendChild(button); + + const formDataWithoutButton = new window.FormData(form); + expect(formDataWithoutButton.get('input')).toBe('testing'); + expect(formDataWithoutButton.get('button')).toBeNull(); + + const formDataWithSubmitter = new window.FormData(form, button); + expect(formDataWithSubmitter.get('input')).toBe('testing'); + expect(formDataWithSubmitter.get('button')).toBe('buttonValue'); }); it('Supports input elements with empty values.', () => {