Repository: capricorn86/happy-dom (ts)

Issue:
Missing window.SubmitEvent
**Is your feature request related to a problem? Please describe.**
Presently, there is no `window.SubmitEvent` available.

**Describe the solution you'd like**
Add one, as has been done for other common event types. Seems like the only attribute you'd need to add is a `SubmitEvent.submitter` property as per [these docs](https://developer.mozilla.org/en-US/docs/Web/API/SubmitEvent).

**Describe alternatives you've considered**
The alternative is to construct a base `Event` and add a `submitter` to it manually.


Pull request description:
#820@minor: Add SubmitEvent.
fix #820

- add class `SubmitEvent`
- `HTMLFormElement.requestSubmit` triggers a `SubmitEvent`
- adapted unit tests

Patch:
diff --git a/packages/happy-dom/src/event/events/ISubmitEventInit.ts b/packages/happy-dom/src/event/events/ISubmitEventInit.ts
new file mode 100644
index 00000000..e46e29e0
--- /dev/null
+++ b/packages/happy-dom/src/event/events/ISubmitEventInit.ts
@@ -0,0 +1,6 @@
+import IHTMLElement from '../../nodes/html-element/IHTMLElement';
+import IEventInit from '../IEventInit';
+
+export default interface ISubmitEventInit extends IEventInit {
+	submitter?: IHTMLElement;
+}
diff --git a/packages/happy-dom/src/event/events/SubmitEvent.ts b/packages/happy-dom/src/event/events/SubmitEvent.ts
new file mode 100644
index 00000000..bdc16f36
--- /dev/null
+++ b/packages/happy-dom/src/event/events/SubmitEvent.ts
@@ -0,0 +1,22 @@
+import Event from '../Event';
+import ISubmitEventInit from './ISubmitEventInit';
+import IHTMLElement from '../../nodes/html-element/IHTMLElement';
+
+/**
+ * An event triggered by form submit buttons.
+ */
+export default class SubmitEvent extends Event {
+	public readonly submitter: IHTMLElement;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param type Event type.
+	 * @param [eventInit] Event init.
+	 */
+	constructor(type: string, eventInit: ISubmitEventInit = null) {
+		super(type, eventInit);
+
+		this.submitter = eventInit?.submitter || null;
+	}
+}
diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts
index 0ac4d28d..5ce8575c 100644
--- a/packages/happy-dom/src/index.ts
+++ b/packages/happy-dom/src/index.ts
@@ -35,11 +35,13 @@ import IInputEventInit from './event/events/IInputEventInit';
 import IKeyboardEventInit from './event/events/IKeyboardEventInit';
 import IMouseEventInit from './event/events/IMouseEventInit';
 import IProgressEventInit from './event/events/IProgressEventInit';
+import ISubmitEventInit from './event/events/ISubmitEventInit';
 import IWheelEventInit from './event/events/IWheelEventInit';
 import InputEvent from './event/events/InputEvent';
 import KeyboardEvent from './event/events/KeyboardEvent';
 import MouseEvent from './event/events/MouseEvent';
 import ProgressEvent from './event/events/ProgressEvent';
+import SubmitEvent from './event/events/SubmitEvent';
 import WheelEvent from './event/events/WheelEvent';
 import DOMParser from './dom-parser/DOMParser';
 import Document from './nodes/document/Document';
@@ -180,11 +182,13 @@ export {
 	IKeyboardEventInit,
 	IMouseEventInit,
 	IProgressEventInit,
+	ISubmitEventInit,
 	IWheelEventInit,
 	InputEvent,
 	KeyboardEvent,
 	MouseEvent,
 	ProgressEvent,
+	SubmitEvent,
 	WheelEvent,
 	DOMParser,
 	Document,
diff --git a/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts b/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts
index 88bb95d4..f0820452 100644
--- a/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts
+++ b/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts
@@ -1,6 +1,7 @@
 import HTMLElement from '../html-element/HTMLElement';
 import IHTMLFormElement from './IHTMLFormElement';
 import Event from '../../event/Event';
+import SubmitEvent from '../../event/events/SubmitEvent';
 import HTMLFormControlsCollection from './HTMLFormControlsCollection';
 import IHTMLFormControlsCollection from './IHTMLFormControlsCollection';
 import INode from '../node/INode';
@@ -209,7 +210,9 @@ export default class HTMLFormElement extends HTMLElement implements IHTMLFormEle
 	public requestSubmit(submitter?: IHTMLInputElement | IHTMLButtonElement): void {
 		const noValidate = submitter?.formNoValidate || this.noValidate;
 		if (noValidate || this.checkValidity()) {
-			this.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
+			this.dispatchEvent(
+				new SubmitEvent('submit', { bubbles: true, cancelable: true, submitter: submitter || this })
+			);
 		}
 	}
 
diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts
index c7f10993..e541b62d 100644
--- a/packages/happy-dom/src/window/IWindow.ts
+++ b/packages/happy-dom/src/window/IWindow.ts
@@ -76,6 +76,7 @@ import InputEvent from '../event/events/InputEvent';
 import UIEvent from '../event/UIEvent';
 import ErrorEvent from '../event/events/ErrorEvent';
 import StorageEvent from '../event/events/StorageEvent';
+import SubmitEvent from '../event/events/SubmitEvent';
 import MessageEvent from '../event/events/MessageEvent';
 import MessagePort from '../event/MessagePort';
 import Screen from '../screen/Screen';
@@ -192,6 +193,7 @@ export default interface IWindow extends IEventTarget, INodeJSGlobal {
 	readonly InputEvent: typeof InputEvent;
 	readonly ErrorEvent: typeof ErrorEvent;
 	readonly StorageEvent: typeof StorageEvent;
+	readonly SubmitEvent: typeof SubmitEvent;
 	readonly MessageEvent: typeof MessageEvent;
 	readonly MessagePort: typeof MessagePort;
 	readonly ProgressEvent: typeof ProgressEvent;
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index 1d9d3913..9bfa1a92 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -83,6 +83,7 @@ import InputEvent from '../event/events/InputEvent';
 import UIEvent from '../event/UIEvent';
 import ErrorEvent from '../event/events/ErrorEvent';
 import StorageEvent from '../event/events/StorageEvent';
+import SubmitEvent from '../event/events/SubmitEvent';
 import Screen from '../screen/Screen';
 import AsyncTaskManager from '../async-task-manager/AsyncTaskManager';
 import IResponse from '../fetch/types/IResponse';
@@ -235,6 +236,7 @@ export default class Window extends EventTarget implements IWindow {
 	public readonly InputEvent = InputEvent;
 	public readonly ErrorEvent = ErrorEvent;
 	public readonly StorageEvent = StorageEvent;
+	public readonly SubmitEvent = SubmitEvent;
 	public readonly ProgressEvent = ProgressEvent;
 	public readonly MediaQueryListEvent = MediaQueryListEvent;
 	public readonly EventTarget = EventTarget;


Test patch:
diff --git a/packages/happy-dom/test/event/events/SubmitEvent.test.ts b/packages/happy-dom/test/event/events/SubmitEvent.test.ts
new file mode 100644
index 00000000..76fc3e49
--- /dev/null
+++ b/packages/happy-dom/test/event/events/SubmitEvent.test.ts
@@ -0,0 +1,16 @@
+import Event from '../../../src/event/Event';
+import SubmitEvent from '../../../src/event/events/SubmitEvent';
+import HTMLButtonElement from '../../../src/nodes/html-button-element/HTMLButtonElement';
+
+describe('SubmitEvent', () => {
+	describe('constructor', () => {
+		it('Creates a submit event.', () => {
+			const submitter = new HTMLButtonElement();
+			const event = new SubmitEvent('submit', { bubbles: true, submitter });
+			expect(event).toBeInstanceOf(Event);
+			expect(event.bubbles).toBe(true);
+			expect(event.cancelable).toBe(false);
+			expect(event.submitter).toBe(submitter);
+		});
+	});
+});
diff --git a/packages/happy-dom/test/nodes/html-form-element/HTMLFormElement.test.ts b/packages/happy-dom/test/nodes/html-form-element/HTMLFormElement.test.ts
index 6d301052..f56301ad 100644
--- a/packages/happy-dom/test/nodes/html-form-element/HTMLFormElement.test.ts
+++ b/packages/happy-dom/test/nodes/html-form-element/HTMLFormElement.test.ts
@@ -4,8 +4,10 @@ import HTMLFormElement from '../../../src/nodes/html-form-element/HTMLFormElemen
 import RadioNodeList from '../../../src/nodes/html-form-element/RadioNodeList';
 import IHTMLInputElement from '../../../src/nodes/html-input-element/IHTMLInputElement';
 import Event from '../../../src/event/Event';
+import SubmitEvent from '../../../src/event/events/SubmitEvent';
 import IHTMLSelectElement from '../../../src/nodes/html-select-element/IHTMLSelectElement';
 import IHTMLTextAreaElement from '../../../src/nodes/html-text-area-element/IHTMLTextAreaElement';
+import IHTMLButtonElement from '../../../src/nodes/html-button-element/IHTMLButtonElement';
 
 describe('HTMLFormElement', () => {
 	let window: Window;
@@ -298,7 +300,9 @@ describe('HTMLFormElement', () => {
 			element.noValidate = true;
 			element.requestSubmit();
 
+			expect(submitEvent).toBeInstanceOf(SubmitEvent);
 			expect(submitEvent.type).toBe('submit');
+			expect((<SubmitEvent>submitEvent).submitter).toBe(element);
 
 			submitEvent = null;
 
@@ -311,9 +315,10 @@ describe('HTMLFormElement', () => {
 			(<IHTMLInputElement>root.children[2]).click();
 			(<IHTMLInputElement>root.children[6]).click();
 
-			element.requestSubmit();
+			element.requestSubmit(<IHTMLButtonElement>root.children[1]);
 
 			expect(submitEvent.type).toBe('submit');
+			expect((<SubmitEvent>submitEvent).submitter).toBe(root.children[1]);
 		});
 
 		it('Skips validating if a submitter is sent that has "formNoValidate" set to "true".', () => {