Repository: capricorn86/happy-dom
Pull request #444 (2022-03-31 18:18:55)

#442@minor: Adds support for HTMLInputElement.setCustomValidity(), HT…
…MLInputElement.reportValidity() and HTMLInputElement.validationMessage.


Patch:
diff --git a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts
index 5f7205d8..7bb6a1d0 100644
--- a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts
+++ b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts
@@ -39,9 +39,11 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE
 	public files: File[] = [];
 
 	// Type specific: text/password/search/tel/url/week/month
-	private _selectionStart = null;
-	private _selectionEnd = null;
-	private _selectionDirection = HTMLInputElementSelectionDirectionEnum.none;
+	private _selectionStart: number = null;
+	private _selectionEnd: number = null;
+	private _selectionDirection: HTMLInputElementSelectionDirectionEnum =
+		HTMLInputElementSelectionDirectionEnum.none;
+	private _validationMessage = '';
 
 	/**
 	 * Returns height.
@@ -736,15 +738,6 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE
 		return new ValidityState(this);
 	}
 
-	/**
-	 * Returns validity message.
-	 *
-	 * @returns Validation message.
-	 */
-	public get validationMessage(): string {
-		return null;
-	}
-
 	/**
 	 * Returns "true" if it will validate.
 	 *
@@ -778,6 +771,38 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE
 		return this.value ? parseFloat(this.value) : NaN;
 	}
 
+	/**
+	 * Returns validation message.
+	 *
+	 * @returns Validation message.
+	 */
+	public get validationMessage(): string {
+		return this._validationMessage;
+	}
+
+	/**
+	 * Sets validation message.
+	 *
+	 * @param message Message.
+	 */
+	public setCustomValidity(message: string): void {
+		this._validationMessage = String(message);
+	}
+
+	/**
+	 * Reports validity by dispatching an "invalid" event.
+	 */
+	public reportValidity(): void {
+		if (this._validationMessage) {
+			this.dispatchEvent(
+				new Event('invalid', {
+					bubbles: true,
+					cancelable: true
+				})
+			);
+		}
+	}
+
 	/**
 	 * Selects the text.
 	 */
diff --git a/packages/happy-dom/src/nodes/html-input-element/IHTMLInputElement.ts b/packages/happy-dom/src/nodes/html-input-element/IHTMLInputElement.ts
index 4c689cd4..8c2b3686 100644
--- a/packages/happy-dom/src/nodes/html-input-element/IHTMLInputElement.ts
+++ b/packages/happy-dom/src/nodes/html-input-element/IHTMLInputElement.ts
@@ -48,10 +48,22 @@ export default interface IHTMLInputElement extends IHTMLElement {
 	selectionEnd: number;
 	selectionDirection: string;
 	validity: ValidityState;
-	validationMessage: string;
 	willValidate: boolean;
 	valueAsDate: Date;
 	valueAsNumber: number;
+	validationMessage: string;
+
+	/**
+	 * Sets validation message.
+	 *
+	 * @param message Message.
+	 */
+	setCustomValidity(message: string): void;
+
+	/**
+	 * Reports validity by dispatching an "invalid" event.
+	 */
+	reportValidity(): void;
 
 	/**
 	 * Set selection range.


Test patch:
diff --git a/packages/happy-dom/test/nodes/html-input-element/HTMLInputElement.test.ts b/packages/happy-dom/test/nodes/html-input-element/HTMLInputElement.test.ts
index 69be8ff6..9e965738 100644
--- a/packages/happy-dom/test/nodes/html-input-element/HTMLInputElement.test.ts
+++ b/packages/happy-dom/test/nodes/html-input-element/HTMLInputElement.test.ts
@@ -3,6 +3,7 @@ import Document from '../../../src/nodes/document/Document';
 import HTMLInputElement from '../../../src/nodes/html-input-element/HTMLInputElement';
 import DOMException from '../../../src/exception/DOMException';
 import File from '../../../src/file/File';
+import Event from '../../../src/event/Event';
 import HTMLInputElementSelectionModeEnum from '../../../src/nodes/html-input-element/HTMLInputElementSelectionModeEnum';
 import HTMLInputElementSelectionDirectionEnum from '../../../src/nodes/html-input-element/HTMLInputElementSelectionDirectionEnum';
 
@@ -381,6 +382,43 @@ describe('HTMLInputElement', () => {
 		});
 	});
 
+	describe('get validationMessage()', () => {
+		it('Returns validation message.', () => {
+			element.setCustomValidity('Error message');
+			expect(element.validationMessage).toBe('Error message');
+		});
+	});
+
+	describe('setCustomValidity()', () => {
+		it('Returns validation message.', () => {
+			element.setCustomValidity('Error message');
+			expect(element.validationMessage).toBe('Error message');
+			element.setCustomValidity(null);
+			expect(element.validationMessage).toBe('null');
+			element.setCustomValidity('');
+			expect(element.validationMessage).toBe('');
+		});
+	});
+
+	describe('reportValidity()', () => {
+		it('Dispatches an "invalid" event.', () => {
+			let dispatchedEvent: Event = null;
+			element.addEventListener('invalid', (event: Event) => (dispatchedEvent = event));
+			element.setCustomValidity('Error message');
+			element.reportValidity();
+			expect(dispatchedEvent.cancelable).toBe(true);
+			expect(dispatchedEvent.bubbles).toBe(true);
+		});
+
+		it('Does not dispatch an "invalid" event if the validation message is an empty string.', () => {
+			let dispatchedEvent: Event = null;
+			element.setCustomValidity('');
+			element.reportValidity();
+			element.addEventListener('invalid', (event: Event) => (dispatchedEvent = event));
+			expect(dispatchedEvent).toBe(null);
+		});
+	});
+
 	describe('setSelectionRange()', () => {
 		it('Sets selection range.', () => {
 			element.value = 'TEST_VALUE';