Repository: capricorn86/happy-dom
Pull request #203 (2021-02-08 23:03:32)

#201@minor: Adds support for HTMLImageElement (<img>).


Patch:
diff --git a/packages/happy-dom/src/config/ElementClass.ts b/packages/happy-dom/src/config/ElementClass.ts
index baafb38a..a642f2ca 100644
--- a/packages/happy-dom/src/config/ElementClass.ts
+++ b/packages/happy-dom/src/config/ElementClass.ts
@@ -6,6 +6,7 @@ import HTMLTextAreaElement from '../nodes/html-text-area-element/HTMLTextAreaEle
 import SVGSVGElement from '../nodes/svg-element/SVGSVGElement';
 import SVGElement from '../nodes/svg-element/SVGElement';
 import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement';
+import HTMLImageElement from '../nodes/html-image-element/HTMLImageElement';
 
 export default {
 	HTMLTemplateElement: HTMLTemplateElement,
@@ -43,7 +44,7 @@ export default {
 	HTMLFieldSetElement: HTMLElement,
 	HTMLFontElement: HTMLElement,
 	HTMLHtmlElement: HTMLElement,
-	HTMLImageElement: HTMLElement,
+	HTMLImageElement: HTMLImageElement,
 	HTMLLabelElement: HTMLElement,
 	HTMLLegendElement: HTMLElement,
 	HTMLMapElement: HTMLElement,
diff --git a/packages/happy-dom/src/config/ElementTag.ts b/packages/happy-dom/src/config/ElementTag.ts
index 8e7b9b98..deb7753f 100644
--- a/packages/happy-dom/src/config/ElementTag.ts
+++ b/packages/happy-dom/src/config/ElementTag.ts
@@ -5,6 +5,7 @@ import HTMLTextAreaElement from '../nodes/html-text-area-element/HTMLTextAreaEle
 import SVGSVGElement from '../nodes/svg-element/SVGSVGElement';
 import SVGElement from '../nodes/svg-element/SVGElement';
 import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement';
+import HTMLImageElement from '../nodes/html-image-element/HTMLImageElement';
 
 export default {
 	template: HTMLTemplateElement,
@@ -12,6 +13,7 @@ export default {
 	input: HTMLInputElement,
 	textarea: HTMLTextAreaElement,
 	script: HTMLScriptElement,
+	img: HTMLImageElement,
 	svg: SVGSVGElement,
 	circle: SVGElement,
 	ellipse: SVGElement,
diff --git a/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts b/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts
new file mode 100644
index 00000000..e260ae7a
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts
@@ -0,0 +1,214 @@
+import HTMLElement from '../html-element/HTMLElement';
+import IHTMLImageElement from './IHTMLImageElement';
+
+/**
+ * HTML Image Element.
+ *
+ * Reference:
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement
+ */
+export default class HTMLImageElement extends HTMLElement implements IHTMLImageElement {
+	public readonly complete = false;
+	public readonly naturalHeight = 0;
+	public readonly naturalWidth = 0;
+	public crossOrigin = null;
+	public decoding = 'auto';
+	public loading = 'auto';
+	public readonly x = 0;
+	public readonly y = 0;
+
+	/**
+	 * Returns alt.
+	 *
+	 * @return Alt.
+	 */
+	public get alt(): string {
+		return this.getAttributeNS(null, 'alt') || '';
+	}
+
+	/**
+	 * Sets alt.
+	 *
+	 * @param alt Alt.
+	 */
+	public set alt(alt: string) {
+		this.setAttributeNS(null, 'alt', alt);
+	}
+
+	/**
+	 * Returns current src.
+	 *
+	 * @return Current src.
+	 */
+	public get currentSrc(): string {
+		return this.src;
+	}
+
+	/**
+	 * Returns height.
+	 *
+	 * @return Height.
+	 */
+	public get height(): string {
+		return this.getAttributeNS(null, 'height') || '';
+	}
+
+	/**
+	 * Sets height.
+	 *
+	 * @param height Height.
+	 */
+	public set height(height: string) {
+		this.setAttributeNS(null, 'height', height);
+	}
+
+	/**
+	 * Returns is map.
+	 *
+	 * @return Is map.
+	 */
+	public get isMap(): boolean {
+		return this.getAttributeNS(null, 'ismap') !== null;
+	}
+
+	/**
+	 * Sets is map.
+	 *
+	 * @param ismap Is map.
+	 */
+	public set isMap(isMap: boolean) {
+		if (!isMap) {
+			this.removeAttributeNS(null, 'ismap');
+		} else {
+			this.setAttributeNS(null, 'ismap', '');
+		}
+	}
+
+	/**
+	 * Returns referrer policy.
+	 *
+	 * @return Referrer policy.
+	 */
+	public get referrerPolicy(): string {
+		return this.getAttributeNS(null, 'referrerpolicy') || '';
+	}
+
+	/**
+	 * Sets referrer policy.
+	 *
+	 * @param referrerPolicy Referrer policy.
+	 */
+	public set referrerPolicy(referrerPolicy: string) {
+		this.setAttributeNS(null, 'referrerpolicy', referrerPolicy);
+	}
+
+	/**
+	 * Returns sizes.
+	 *
+	 * @return Sizes.
+	 */
+	public get sizes(): string {
+		return this.getAttributeNS(null, 'sizes') || '';
+	}
+
+	/**
+	 * Sets sizes.
+	 *
+	 * @param sizes Sizes.
+	 */
+	public set sizes(sizes: string) {
+		this.setAttributeNS(null, 'sizes', sizes);
+	}
+
+	/**
+	 * Returns source.
+	 *
+	 * @return Source.
+	 */
+	public get src(): string {
+		return this.getAttributeNS(null, 'src') || '';
+	}
+
+	/**
+	 * Sets source.
+	 *
+	 * @param source Source.
+	 */
+	public set src(src: string) {
+		this.setAttributeNS(null, 'src', src);
+	}
+
+	/**
+	 * Returns srcset.
+	 *
+	 * @return Source.
+	 */
+	public get srcset(): string {
+		return this.getAttributeNS(null, 'srcset') || '';
+	}
+
+	/**
+	 * Sets src set.
+	 *
+	 * @param srcset Src set.
+	 */
+	public set srcset(srcset: string) {
+		this.setAttributeNS(null, 'srcset', srcset);
+	}
+
+	/**
+	 * Returns use map.
+	 *
+	 * @return Use map.
+	 */
+	public get useMap(): string {
+		return this.getAttributeNS(null, 'usemap') || '';
+	}
+
+	/**
+	 * Sets is map.
+	 *
+	 * @param useMap Is map.
+	 */
+	public set useMap(useMap: string) {
+		this.setAttributeNS(null, 'usemap', useMap);
+	}
+
+	/**
+	 * Returns width.
+	 *
+	 * @return Width.
+	 */
+	public get width(): string {
+		return this.getAttributeNS(null, 'width') || '';
+	}
+
+	/**
+	 * Sets width.
+	 *
+	 * @param width Width.
+	 */
+	public set width(width: string) {
+		this.setAttributeNS(null, 'width', width);
+	}
+
+	/**
+	 * The decode() method of the HTMLImageElement interface returns a Promise that resolves when the image is decoded and it is safe to append the image to the DOM.
+	 *
+	 * @return Promise.
+	 */
+	public decode(): Promise<void> {
+		return Promise.resolve();
+	}
+
+	/**
+	 * Clones a node.
+	 *
+	 * @override
+	 * @param [deep=false] "true" to clone deep.
+	 * @return Cloned node.
+	 */
+	public cloneNode(deep = false): IHTMLImageElement {
+		return <IHTMLImageElement>super.cloneNode(deep);
+	}
+}
diff --git a/packages/happy-dom/src/nodes/html-image-element/IHTMLImageElement.ts b/packages/happy-dom/src/nodes/html-image-element/IHTMLImageElement.ts
new file mode 100644
index 00000000..50e47749
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-image-element/IHTMLImageElement.ts
@@ -0,0 +1,44 @@
+import IHTMLElement from '../html-element/IHTMLElement';
+
+/**
+ * HTML Image Element.
+ *
+ * Reference:
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement
+ */
+export default interface IHTMLImageElement extends IHTMLElement {
+	alt: string;
+	readonly complete: boolean;
+	crossOrigin: string;
+	readonly currentSrc: string;
+	decoding: string;
+	height: string;
+	isMap: boolean;
+	loading: string;
+	readonly naturalHeight: number;
+	readonly naturalWidth: number;
+	referrerPolicy: string;
+	sizes: string;
+	src: string;
+	srcset: string;
+	useMap: string;
+	width: string;
+	readonly x: number;
+	readonly y: number;
+
+	/**
+	 * The decode() method of the HTMLImageElement interface returns a Promise that resolves when the image is decoded and it is safe to append the image to the DOM.
+	 *
+	 * @return Promise.
+	 */
+	decode(): Promise<void>;
+
+	/**
+	 * Clones a node.
+	 *
+	 * @override
+	 * @param [deep=false] "true" to clone deep.
+	 * @return Cloned node.
+	 */
+	cloneNode(deep: boolean): IHTMLImageElement;
+}
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index 41b2c0e8..96a871bf 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -6,7 +6,15 @@ import Text from '../nodes/text/Text';
 import Comment from '../nodes/comment/Comment';
 import ShadowRoot from '../nodes/shadow-root/ShadowRoot';
 import Element from '../nodes/element/Element';
+import HTMLTemplateElement from '../nodes/html-template-element/HTMLTemplateElement';
+import HTMLFormElement from '../nodes/html-form-element/HTMLFormElement';
 import HTMLElement from '../nodes/html-element/HTMLElement';
+import HTMLInputElement from '../nodes/html-input-element/HTMLInputElement';
+import HTMLTextAreaElement from '../nodes/html-text-area-element/HTMLTextAreaElement';
+import SVGSVGElement from '../nodes/svg-element/SVGSVGElement';
+import SVGElement from '../nodes/svg-element/SVGElement';
+import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement';
+import HTMLImageElement from '../nodes/html-image-element/HTMLImageElement';
 import DocumentFragment from '../nodes/document-fragment/DocumentFragment';
 import TreeWalker from '../tree-walker/TreeWalker';
 import Event from '../event/Event';
@@ -47,6 +55,14 @@ export default class Window extends EventTarget implements NodeJS.Global {
 	// Global classes
 	public Node = Node;
 	public HTMLElement = HTMLElement;
+	public HTMLTemplateElement = HTMLTemplateElement;
+	public HTMLFormElement = HTMLFormElement;
+	public HTMLInputElement = HTMLInputElement;
+	public HTMLTextAreaElement = HTMLTextAreaElement;
+	public HTMLImageElement = HTMLImageElement;
+	public HTMLScriptElement = HTMLScriptElement;
+	public SVGSVGElement = SVGSVGElement;
+	public SVGElement = SVGElement;
 	public Text = Text;
 	public Comment = Comment;
 	public ShadowRoot = ShadowRoot;


Test patch:
diff --git a/packages/happy-dom/test/nodes/html-image-element/HTMLImageElement.test.ts b/packages/happy-dom/test/nodes/html-image-element/HTMLImageElement.test.ts
new file mode 100644
index 00000000..50ab4f64
--- /dev/null
+++ b/packages/happy-dom/test/nodes/html-image-element/HTMLImageElement.test.ts
@@ -0,0 +1,121 @@
+import Window from '../../../src/window/Window';
+import Document from '../../../src/nodes/document/Document';
+import HTMLImageElement from '../../../src/nodes/html-image-element/HTMLImageElement';
+
+describe('HTMLImageElement', () => {
+	let window: Window;
+	let document: Document;
+
+	beforeEach(() => {
+		window = new Window();
+		document = window.document;
+	});
+
+	for (const property of [
+		'alt',
+		'height',
+		'referrerPolicy',
+		'sizes',
+		'src',
+		'srcset',
+		'useMap',
+		'width'
+	]) {
+		describe(`get ${property}()`, () => {
+			test(`Returns the "${property}" attribute.`, () => {
+				const element = document.createElement('img');
+				element.setAttribute(property, 'test');
+				expect(element[property]).toBe('test');
+			});
+		});
+
+		describe(`set ${property}()`, () => {
+			test(`Sets the attribute "${property}".`, () => {
+				const element = document.createElement('img');
+				element[property] = 'test';
+				expect(element.getAttribute(property)).toBe('test');
+			});
+		});
+	}
+
+	for (const property of ['isMap']) {
+		describe(`get ${property}()`, () => {
+			test(`Returns "true" if the "${property}" attribute is defined.`, () => {
+				const element = document.createElement('img');
+				element.setAttribute(property, '');
+				expect(element[property]).toBe(true);
+			});
+		});
+
+		describe(`set ${property}()`, () => {
+			test(`Sets the "${property}" attribute to an empty string if set to "true".`, () => {
+				const element = document.createElement('img');
+				element[property] = true;
+				expect(element.getAttribute(property)).toBe('');
+			});
+		});
+	}
+
+	describe('get complete()', () => {
+		test('Returns "false".', () => {
+			const element = <HTMLImageElement>document.createElement('img');
+			expect(element.complete).toBe(false);
+		});
+	});
+
+	describe('get naturalHeight()', () => {
+		test('Returns "0".', () => {
+			const element = <HTMLImageElement>document.createElement('img');
+			expect(element.naturalHeight).toBe(0);
+		});
+	});
+
+	describe('get naturalWidth()', () => {
+		test('Returns "0".', () => {
+			const element = <HTMLImageElement>document.createElement('img');
+			expect(element.naturalWidth).toBe(0);
+		});
+	});
+
+	describe('get crossOrigin()', () => {
+		test('Returns "null".', () => {
+			const element = <HTMLImageElement>document.createElement('img');
+			expect(element.crossOrigin).toBe(null);
+		});
+	});
+
+	describe('get decoding()', () => {
+		test('Returns "auto".', () => {
+			const element = <HTMLImageElement>document.createElement('img');
+			expect(element.decoding).toBe('auto');
+		});
+	});
+
+	describe('get loading()', () => {
+		test('Returns "auto".', () => {
+			const element = <HTMLImageElement>document.createElement('img');
+			expect(element.loading).toBe('auto');
+		});
+	});
+
+	describe('get x()', () => {
+		test('Returns "0".', () => {
+			const element = <HTMLImageElement>document.createElement('img');
+			expect(element.x).toBe(0);
+		});
+	});
+
+	describe('get y()', () => {
+		test('Returns "0".', () => {
+			const element = <HTMLImageElement>document.createElement('img');
+			expect(element.y).toBe(0);
+		});
+	});
+
+	describe('decode()', () => {
+		test('Executes a promise.', async () => {
+			const element = <HTMLImageElement>document.createElement('img');
+			await element.decode();
+		});
+	});
+});