Repository: capricorn86/happy-dom
Pull request #236 (2021-03-16 23:18:21)

#232@minor: Adds support for Element.setAttributeNodeNS(), Element.re…
…moveAttributeNodeNS(), Element.getAttributeNodeNS().


Patch:
diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts
index 8761fbcf..142144d4 100644
--- a/packages/happy-dom/src/nodes/element/Element.ts
+++ b/packages/happy-dom/src/nodes/element/Element.ts
@@ -511,11 +511,9 @@ export default class Element extends Node implements IElement {
 	 * @param localName Local name.
 	 */
 	public getAttributeNS(namespace: string, localName: string): string {
-		for (const name of Object.keys(this._attributes)) {
-			const attribute = this._attributes[name];
-			if (attribute.namespaceURI === namespace && attribute.localName === localName) {
-				return attribute.value;
-			}
+		const attribute = this.getAttributeNodeNS(namespace, localName);
+		if (attribute) {
+			return attribute.value;
 		}
 		return null;
 	}
@@ -731,6 +729,16 @@ export default class Element extends Node implements IElement {
 		return replacedAttribute || null;
 	}
 
+	/**
+	 * The setAttributeNodeNS() method adds a new Attr node to the specified element.
+	 *
+	 * @param attribute Attribute.
+	 * @returns Replaced attribute.
+	 */
+	public setAttributeNodeNS(attribute: Attr): Attr {
+		return this.setAttributeNode(attribute);
+	}
+
 	/**
 	 * Returns an Attr node.
 	 *
@@ -741,6 +749,24 @@ export default class Element extends Node implements IElement {
 		return this._attributes[this._getAttributeName(name)] || null;
 	}
 
+	/**
+	 * Returns a namespaced Attr node.
+	 *
+	 * @param namespace Namespace.
+	 * @param name Name.
+	 * @returns Replaced attribute.
+	 */
+	public getAttributeNodeNS(namespace: string, name: string): Attr {
+		const attributeName = this._getAttributeName(name);
+		for (const name of Object.keys(this._attributes)) {
+			const attribute = this._attributes[name];
+			if (attribute.namespaceURI === namespace && attribute.localName === attributeName) {
+				return attribute;
+			}
+		}
+		return null;
+	}
+
 	/**
 	 * Removes an Attr node.
 	 *
@@ -775,6 +801,15 @@ export default class Element extends Node implements IElement {
 		}
 	}
 
+	/**
+	 * Removes an Attr node.
+	 *
+	 * @param attribute Attribute.
+	 */
+	public removeAttributeNodeNS(attribute: Attr): void {
+		this.removeAttributeNode(attribute);
+	}
+
 	/**
 	 * Scrolls to a particular set of coordinates.
 	 *
diff --git a/packages/happy-dom/src/nodes/element/IElement.ts b/packages/happy-dom/src/nodes/element/IElement.ts
index a5670ec6..656b2912 100644
--- a/packages/happy-dom/src/nodes/element/IElement.ts
+++ b/packages/happy-dom/src/nodes/element/IElement.ts
@@ -160,6 +160,14 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode,
 	 */
 	setAttributeNode(attribute: Attr): Attr;
 
+	/**
+	 * The setAttributeNodeNS() method adds a new Attr node to the specified element.
+	 *
+	 * @param attribute Attribute.
+	 * @returns Replaced attribute.
+	 */
+	setAttributeNodeNS(attribute: Attr): Attr;
+
 	/**
 	 * Returns an Attr node.
 	 *
@@ -168,6 +176,15 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode,
 	 */
 	getAttributeNode(name: string): Attr;
 
+	/**
+	 * Returns a namespaced Attr node.
+	 *
+	 * @param namespace Namespace.
+	 * @param nodeName Node name.
+	 * @returns Replaced attribute.
+	 */
+	getAttributeNodeNS(namespace: string, nodeName: string): Attr;
+
 	/**
 	 * Removes an Attr node.
 	 *
@@ -175,6 +192,13 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode,
 	 */
 	removeAttributeNode(attribute: Attr): void;
 
+	/**
+	 * Removes an Attr node.
+	 *
+	 * @param attribute Attribute.
+	 */
+	removeAttributeNodeNS(attribute: Attr): void;
+
 	/**
 	 * Clones a node.
 	 *


Test patch:
diff --git a/packages/happy-dom/test/nodes/element/Element.test.ts b/packages/happy-dom/test/nodes/element/Element.test.ts
index e5047bbb..599602b9 100644
--- a/packages/happy-dom/test/nodes/element/Element.test.ts
+++ b/packages/happy-dom/test/nodes/element/Element.test.ts
@@ -1050,8 +1050,83 @@ describe('Element', () => {
 		});
 	});
 
-	describe('setAttributeNode()', () => {
-		test('Sets an Attr node on a <div> element.', () => {
+	for (const method of ['setAttributeNode', 'setAttributeNodeNS']) {
+		describe(`${method}()`, () => {
+			test('Sets an Attr node on a <div> element.', () => {
+				const attribute1 = document.createAttributeNS(NamespaceURI.svg, 'KEY1');
+				const attribute2 = document.createAttribute('KEY2');
+
+				attribute1.value = 'value1';
+				attribute2.value = 'value2';
+
+				element[method](attribute1);
+				element[method](attribute2);
+
+				expect(element.attributes).toEqual({
+					'0': {
+						name: 'key1',
+						namespaceURI: NamespaceURI.svg,
+						value: 'value1'
+					},
+					'1': {
+						name: 'key2',
+						namespaceURI: null,
+						value: 'value2'
+					},
+					key1: {
+						name: 'key1',
+						namespaceURI: NamespaceURI.svg,
+						value: 'value1'
+					},
+					key2: {
+						name: 'key2',
+						namespaceURI: null,
+						value: 'value2'
+					},
+					length: 2
+				});
+			});
+
+			test('Sets an Attr node on an <svg> element.', () => {
+				const svg = document.createElementNS(NamespaceURI.svg, 'svg');
+				const attribute1 = document.createAttributeNS(NamespaceURI.svg, 'KEY1');
+				const attribute2 = document.createAttribute('KEY2');
+
+				attribute1.value = 'value1';
+				attribute2.value = 'value2';
+
+				svg[method](attribute1);
+				svg[method](attribute2);
+
+				expect(svg.attributes).toEqual({
+					'0': {
+						name: 'KEY1',
+						namespaceURI: NamespaceURI.svg,
+						value: 'value1'
+					},
+					'1': {
+						name: 'key2',
+						namespaceURI: null,
+						value: 'value2'
+					},
+					KEY1: {
+						name: 'KEY1',
+						namespaceURI: NamespaceURI.svg,
+						value: 'value1'
+					},
+					key2: {
+						name: 'key2',
+						namespaceURI: null,
+						value: 'value2'
+					},
+					length: 2
+				});
+			});
+		});
+	}
+
+	describe(`getAttributeNode()`, () => {
+		test('Returns an Attr node from a <div> element.', () => {
 			const attribute1 = document.createAttributeNS(NamespaceURI.svg, 'KEY1');
 			const attribute2 = document.createAttribute('KEY2');
 
@@ -1061,32 +1136,13 @@ describe('Element', () => {
 			element.setAttributeNode(attribute1);
 			element.setAttributeNode(attribute2);
 
-			expect(element.attributes).toEqual({
-				'0': {
-					name: 'key1',
-					namespaceURI: NamespaceURI.svg,
-					value: 'value1'
-				},
-				'1': {
-					name: 'key2',
-					namespaceURI: null,
-					value: 'value2'
-				},
-				key1: {
-					name: 'key1',
-					namespaceURI: NamespaceURI.svg,
-					value: 'value1'
-				},
-				key2: {
-					name: 'key2',
-					namespaceURI: null,
-					value: 'value2'
-				},
-				length: 2
-			});
+			expect(element.getAttributeNode('key1')).toBe(attribute1);
+			expect(element.getAttributeNode('key2')).toBe(attribute2);
+			expect(element.getAttributeNode('KEY1')).toBe(attribute1);
+			expect(element.getAttributeNode('KEY2')).toBe(attribute2);
 		});
 
-		test('Sets an Attr node on an <svg> element.', () => {
+		test('Returns an Attr node from an <svg> element.', () => {
 			const svg = document.createElementNS(NamespaceURI.svg, 'svg');
 			const attribute1 = document.createAttributeNS(NamespaceURI.svg, 'KEY1');
 			const attribute2 = document.createAttribute('KEY2');
@@ -1097,78 +1153,52 @@ describe('Element', () => {
 			svg.setAttributeNode(attribute1);
 			svg.setAttributeNode(attribute2);
 
-			expect(svg.attributes).toEqual({
-				'0': {
-					name: 'KEY1',
-					namespaceURI: NamespaceURI.svg,
-					value: 'value1'
-				},
-				'1': {
-					name: 'key2',
-					namespaceURI: null,
-					value: 'value2'
-				},
-				KEY1: {
-					name: 'KEY1',
-					namespaceURI: NamespaceURI.svg,
-					value: 'value1'
-				},
-				key2: {
-					name: 'key2',
-					namespaceURI: null,
-					value: 'value2'
-				},
-				length: 2
-			});
+			expect(svg.getAttributeNode('key1')).toBe(null);
+			expect(svg.getAttributeNode('key2')).toBe(attribute2);
+			expect(svg.getAttributeNode('KEY1')).toBe(attribute1);
+			expect(svg.getAttributeNode('KEY2')).toBe(null);
 		});
 	});
 
-	describe('getAttributeNode()', () => {
-		test('Returns an Attr node from a <div> element.', () => {
+	describe(`getAttributeNode()`, () => {
+		test('Returns a namespaced Attr node from a <div> element.', () => {
 			const attribute1 = document.createAttributeNS(NamespaceURI.svg, 'KEY1');
-			const attribute2 = document.createAttribute('KEY2');
 
 			attribute1.value = 'value1';
-			attribute2.value = 'value2';
 
 			element.setAttributeNode(attribute1);
-			element.setAttributeNode(attribute2);
 
-			expect(element.getAttributeNode('key1')).toBe(attribute1);
-			expect(element.getAttributeNode('key2')).toBe(attribute2);
-			expect(element.getAttributeNode('KEY1')).toBe(attribute1);
-			expect(element.getAttributeNode('KEY2')).toBe(attribute2);
+			expect(element.getAttributeNodeNS(NamespaceURI.svg, 'key1')).toBe(attribute1);
+			expect(element.getAttributeNodeNS(NamespaceURI.svg, 'KEY1')).toBe(attribute1);
 		});
 
 		test('Returns an Attr node from an <svg> element.', () => {
 			const svg = document.createElementNS(NamespaceURI.svg, 'svg');
 			const attribute1 = document.createAttributeNS(NamespaceURI.svg, 'KEY1');
-			const attribute2 = document.createAttribute('KEY2');
 
 			attribute1.value = 'value1';
-			attribute2.value = 'value2';
 
 			svg.setAttributeNode(attribute1);
-			svg.setAttributeNode(attribute2);
 
-			expect(svg.getAttributeNode('key1')).toBe(null);
-			expect(svg.getAttributeNode('key2')).toBe(attribute2);
-			expect(svg.getAttributeNode('KEY1')).toBe(attribute1);
-			expect(svg.getAttributeNode('KEY2')).toBe(null);
+			expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'key1')).toBe(null);
+			expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'KEY1')).toBe(attribute1);
+			expect(svg.getAttributeNodeNS(NamespaceURI.svg, 'KEY2')).toBe(null);
 		});
 	});
 
-	describe('removeAttributeNode()', () => {
-		test('Removes an Attr node.', () => {
-			const attribute = document.createAttribute('KEY1');
+	for (const method of ['removeAttributeNode', 'removeAttributeNodeNS']) {
+		describe(`${method}()`, () => {
+			test('Removes an Attr node.', () => {
+				const attribute = document.createAttribute('KEY1');
 
-			attribute.value = 'value1';
-			element.setAttributeNode(attribute);
-			element.removeAttributeNode(attribute);
+				attribute.value = 'value1';
+				element.setAttributeNode(attribute);
+				element[method](attribute);
 
-			expect(element.attributes).toEqual({ length: 0 });
+				expect(element.attributes).toEqual({ length: 0 });
+			});
 		});
-	});
+	}
 
 	describe('replaceWith()', () => {
 		test('Replaces a node with another node.', () => {