Repository: capricorn86/happy-dom
Pull request #445 (2022-03-31 21:29:22)

#429@minor: Adds support for HTMLBaseElement. Adds support for Node.b…
…aseURI.


Patch:
diff --git a/packages/happy-dom/src/config/ElementTag.ts b/packages/happy-dom/src/config/ElementTag.ts
index c9ce338c..0044d86c 100644
--- a/packages/happy-dom/src/config/ElementTag.ts
+++ b/packages/happy-dom/src/config/ElementTag.ts
@@ -12,6 +12,7 @@ import HTMLStyleElement from '../nodes/html-style-element/HTMLStyleElement';
 import HTMLLabelElement from '../nodes/html-label-element/HTMLLabelElement';
 import HTMLSlotElement from '../nodes/html-slot-element/HTMLSlotElement';
 import HTMLMetaElement from '../nodes/html-meta-element/HTMLMetaElement';
+import HTMLBaseElement from '../nodes/html-base-element/HTMLBaseElement';
 
 export default {
 	A: HTMLElement,
@@ -22,7 +23,7 @@ export default {
 	ASIDE: HTMLElement,
 	AUDIO: HTMLElement,
 	B: HTMLElement,
-	BASE: HTMLElement,
+	BASE: HTMLBaseElement,
 	BDI: HTMLElement,
 	BDO: HTMLElement,
 	BLOCKQUAOTE: HTMLElement,
diff --git a/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts b/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts
index 2542b897..98c17706 100644
--- a/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts
+++ b/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts
@@ -1,7 +1,6 @@
 export default [
 	'HTMLHeadElement',
 	'HTMLTitleElement',
-	'HTMLBaseElement',
 	'HTMLMetaElement',
 	'HTMLBodyElement',
 	'HTMLHeadingElement',
diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts
index b7b9cc02..912825e4 100644
--- a/packages/happy-dom/src/index.ts
+++ b/packages/happy-dom/src/index.ts
@@ -74,6 +74,8 @@ import HTMLLabelElement from './nodes/html-label-element/HTMLLabelElement';
 import IHTMLLabelElement from './nodes/html-label-element/IHTMLLabelElement';
 import HTMLMetaElement from './nodes/html-meta-element/HTMLMetaElement';
 import IHTMLMetaElement from './nodes/html-meta-element/IHTMLMetaElement';
+import HTMLBaseElement from './nodes/html-base-element/HTMLBaseElement';
+import IHTMLBaseElement from './nodes/html-base-element/IHTMLBaseElement';
 import SVGElement from './nodes/svg-element/SVGElement';
 import ISVGElement from './nodes/svg-element/ISVGElement';
 import SVGGraphicsElement from './nodes/svg-element/SVGGraphicsElement';
@@ -179,6 +181,8 @@ export {
 	IHTMLLabelElement,
 	HTMLMetaElement,
 	IHTMLMetaElement,
+	HTMLBaseElement,
+	IHTMLBaseElement,
 	SVGElement,
 	ISVGElement,
 	SVGGraphicsElement,
diff --git a/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts b/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts
new file mode 100644
index 00000000..2453301d
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts
@@ -0,0 +1,61 @@
+import HTMLElement from '../html-element/HTMLElement';
+import IHTMLBaseElement from './IHTMLBaseElement';
+
+/**
+ * HTML Base Element.
+ *
+ * Reference:
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base.
+ */
+export default class HTMLBaseElement extends HTMLElement implements IHTMLBaseElement {
+	/**
+	 * Returns href.
+	 *
+	 * @returns Href.
+	 */
+	public get href(): string {
+		const href = this.getAttributeNS(null, 'href');
+		if (href !== null) {
+			return href;
+		}
+		return this.ownerDocument.location.href;
+	}
+
+	/**
+	 * Sets href.
+	 *
+	 * @param href Href.
+	 */
+	public set href(href: string) {
+		this.setAttributeNS(null, 'href', href);
+	}
+
+	/**
+	 * Returns target.
+	 *
+	 * @returns Target.
+	 */
+	public get target(): string {
+		return this.getAttributeNS(null, 'target') || '';
+	}
+
+	/**
+	 * Sets target.
+	 *
+	 * @param target Target.
+	 */
+	public set target(target: string) {
+		this.setAttributeNS(null, 'target', target);
+	}
+
+	/**
+	 * Clones a node.
+	 *
+	 * @override
+	 * @param [deep=false] "true" to clone deep.
+	 * @returns Cloned node.
+	 */
+	public cloneNode(deep = false): IHTMLBaseElement {
+		return <IHTMLBaseElement>super.cloneNode(deep);
+	}
+}
diff --git a/packages/happy-dom/src/nodes/html-base-element/IHTMLBaseElement.ts b/packages/happy-dom/src/nodes/html-base-element/IHTMLBaseElement.ts
new file mode 100644
index 00000000..6d991c9c
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-base-element/IHTMLBaseElement.ts
@@ -0,0 +1,21 @@
+import IHTMLElement from '../html-element/IHTMLElement';
+
+/**
+ * HTML Base Element.
+ *
+ * Reference:
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base.
+ */
+export default interface IHTMLBaseElement extends IHTMLElement {
+	href: string;
+	target: string;
+
+	/**
+	 * Clones a node.
+	 *
+	 * @override
+	 * @param [deep=false] "true" to clone deep.
+	 * @returns Cloned node.
+	 */
+	cloneNode(deep: boolean): IHTMLBaseElement;
+}
diff --git a/packages/happy-dom/src/nodes/node/INode.ts b/packages/happy-dom/src/nodes/node/INode.ts
index 71d52398..bb0a0dc5 100644
--- a/packages/happy-dom/src/nodes/node/INode.ts
+++ b/packages/happy-dom/src/nodes/node/INode.ts
@@ -21,6 +21,7 @@ export default interface INode extends IEventTarget {
 	readonly nextSibling: INode;
 	readonly firstChild: INode;
 	readonly lastChild: INode;
+	readonly baseURI: string;
 	nodeValue: string;
 	textContent: string;
 	connectedCallback?(): void;
diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts
index 4c1a4238..54b36b8e 100644
--- a/packages/happy-dom/src/nodes/node/Node.ts
+++ b/packages/happy-dom/src/nodes/node/Node.ts
@@ -7,6 +7,7 @@ import INode from './INode';
 import DOMException from '../../exception/DOMException';
 import IDocument from '../document/IDocument';
 import IElement from '../element/IElement';
+import IHTMLBaseElement from '../html-base-element/IHTMLBaseElement';
 import INodeList from './INodeList';
 import NodeListFactory from './NodeListFactory';
 
@@ -156,6 +157,19 @@ export default class Node extends EventTarget implements INode {
 		return <IElement>parent;
 	}
 
+	/**
+	 * Returns base URI.
+	 *
+	 * @returns Base URI.
+	 */
+	public get baseURI(): string {
+		const base = <IHTMLBaseElement>this.ownerDocument.querySelector('base');
+		if (base) {
+			return base.href;
+		}
+		return this.ownerDocument.location.href;
+	}
+
 	/**
 	 * Connected callback.
 	 */
diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts
index cacfb9c3..46a0b199 100644
--- a/packages/happy-dom/src/window/IWindow.ts
+++ b/packages/happy-dom/src/window/IWindow.ts
@@ -19,6 +19,7 @@ import HTMLStyleElement from '../nodes/html-style-element/HTMLStyleElement';
 import HTMLSlotElement from '../nodes/html-slot-element/HTMLSlotElement';
 import HTMLLabelElement from '../nodes/html-label-element/HTMLLabelElement';
 import HTMLMetaElement from '../nodes/html-meta-element/HTMLMetaElement';
+import HTMLBaseElement from '../nodes/html-base-element/HTMLBaseElement';
 import SVGSVGElement from '../nodes/svg-element/SVGSVGElement';
 import SVGElement from '../nodes/svg-element/SVGElement';
 import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement';
@@ -106,6 +107,7 @@ export default interface IWindow extends IEventTarget {
 	readonly HTMLSlotElement: typeof HTMLSlotElement;
 	readonly HTMLLabelElement: typeof HTMLLabelElement;
 	readonly HTMLMetaElement: typeof HTMLMetaElement;
+	readonly HTMLBaseElement: typeof HTMLBaseElement;
 	readonly SVGSVGElement: typeof SVGSVGElement;
 	readonly SVGElement: typeof SVGElement;
 	readonly Image: typeof Image;
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index 1de55cb5..a7b1f668 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -20,6 +20,7 @@ import HTMLStyleElement from '../nodes/html-style-element/HTMLStyleElement';
 import HTMLSlotElement from '../nodes/html-slot-element/HTMLSlotElement';
 import HTMLLabelElement from '../nodes/html-label-element/HTMLLabelElement';
 import HTMLMetaElement from '../nodes/html-meta-element/HTMLMetaElement';
+import HTMLBaseElement from '../nodes/html-base-element/HTMLBaseElement';
 import SVGSVGElement from '../nodes/svg-element/SVGSVGElement';
 import SVGElement from '../nodes/svg-element/SVGElement';
 import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement';
@@ -116,6 +117,7 @@ export default class Window extends EventTarget implements IWindow, NodeJS.Globa
 	public readonly HTMLLabelElement = HTMLLabelElement;
 	public readonly HTMLSlotElement = HTMLSlotElement;
 	public readonly HTMLMetaElement = HTMLMetaElement;
+	public readonly HTMLBaseElement = HTMLBaseElement;
 	public readonly SVGSVGElement = SVGSVGElement;
 	public readonly SVGElement = SVGElement;
 	public readonly Text = Text;


Test patch:
diff --git a/packages/happy-dom/test/nodes/html-base-element/HTMLBaseElement.test.ts b/packages/happy-dom/test/nodes/html-base-element/HTMLBaseElement.test.ts
new file mode 100644
index 00000000..ac9e854a
--- /dev/null
+++ b/packages/happy-dom/test/nodes/html-base-element/HTMLBaseElement.test.ts
@@ -0,0 +1,52 @@
+import Window from '../../../src/window/Window';
+import IWindow from '../../../src/window/IWindow';
+import IDocument from '../../../src/nodes/document/IDocument';
+import IHTMLBaseElement from '../../../src/nodes/html-base-element/IHTMLBaseElement';
+
+describe('HTMLBaseElement', () => {
+	let window: IWindow;
+	let document: IDocument;
+
+	beforeEach(() => {
+		window = new Window();
+		document = window.document;
+	});
+
+	describe('get target()', () => {
+		it('Returns the "target" attribute.', () => {
+			const element = <IHTMLBaseElement>document.createElement('base');
+			element.setAttribute('target', 'test');
+			expect(element.target).toBe('test');
+		});
+	});
+
+	describe('set target()', () => {
+		it('Sets the attribute "target".', () => {
+			const element = <IHTMLBaseElement>document.createElement('base');
+			element.target = 'test';
+			expect(element.getAttribute('target')).toBe('test');
+		});
+	});
+
+	describe('get href()', () => {
+		it('Returns the "href" attribute.', () => {
+			const element = <IHTMLBaseElement>document.createElement('base');
+			element.setAttribute('href', 'test');
+			expect(element.href).toBe('test');
+		});
+
+		it('Returns location.href if not set.', () => {
+			const element = <IHTMLBaseElement>document.createElement('base');
+			document.location.href = 'https://localhost:8080/base/path/to/script/?key=value=1#test';
+			expect(element.href).toBe('https://localhost:8080/base/path/to/script/?key=value=1#test');
+		});
+	});
+
+	describe('set href()', () => {
+		it('Sets the attribute "href".', () => {
+			const element = <IHTMLBaseElement>document.createElement('base');
+			element.href = 'test';
+			expect(element.getAttribute('href')).toBe('test');
+		});
+	});
+});
diff --git a/packages/happy-dom/test/nodes/node/Node.test.ts b/packages/happy-dom/test/nodes/node/Node.test.ts
index 9ebbf31c..d31a756e 100644
--- a/packages/happy-dom/test/nodes/node/Node.test.ts
+++ b/packages/happy-dom/test/nodes/node/Node.test.ts
@@ -198,6 +198,26 @@ describe('Node', () => {
 		});
 	});
 
+	describe('get baseURI()', () => {
+		it('Returns location.href.', () => {
+			document.location.href = 'https://localhost:8080/base/path/to/script/?key=value=1#test';
+
+			const div = document.createElement('div');
+			expect(div.baseURI).toBe('https://localhost:8080/base/path/to/script/?key=value=1#test');
+		});
+
+		it('Returns the "href" attribute set on a <base> element.', () => {
+			document.location.href = 'https://localhost:8080/base/path/to/script/?key=value=1#test';
+
+			const base = document.createElement('base');
+			base.setAttribute('href', 'https://www.test.test/base/path/to/script/?key=value=1#test');
+			document.documentElement.appendChild(base);
+
+			const div = document.createElement('div');
+			expect(div.baseURI).toBe('https://www.test.test/base/path/to/script/?key=value=1#test');
+		});
+	});
+
 	describe('connectedCallback()', () => {
 		it('Calls connected callback when a custom element is connected to DOM.', () => {
 			document.body.innerHTML = '<custom-counter><custom-button></custom-button></custom-counter>';