Repository: capricorn86/happy-dom
Pull request #872 (2023-04-21 13:57:39)

#699@minor: Adds support for SVGElement.focus(), SVGElement.blur() an…
…d SVGElement.tabIndex.


Patch:
diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
index b45b1719..111c6a35 100644
--- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
+++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
@@ -2,12 +2,12 @@ import Element from '../element/Element';
 import IHTMLElement from './IHTMLElement';
 import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration';
 import IAttr from '../attr/IAttr';
-import FocusEvent from '../../event/events/FocusEvent';
 import PointerEvent from '../../event/events/PointerEvent';
 import Dataset from '../element/Dataset';
 import NodeTypeEnum from '../node/NodeTypeEnum';
 import DOMException from '../../exception/DOMException';
 import Event from '../../event/Event';
+import HTMLElementUtility from './HTMLElementUtility';
 
 /**
  * HTML Element.
@@ -312,52 +312,14 @@ export default class HTMLElement extends Element implements IHTMLElement {
 	 * Triggers a blur event.
 	 */
 	public blur(): void {
-		if (this.ownerDocument['_activeElement'] !== this || !this.isConnected) {
-			return;
-		}
-
-		this.ownerDocument['_activeElement'] = null;
-
-		this.dispatchEvent(
-			new FocusEvent('blur', {
-				bubbles: false,
-				composed: true
-			})
-		);
-		this.dispatchEvent(
-			new FocusEvent('focusout', {
-				bubbles: true,
-				composed: true
-			})
-		);
+		HTMLElementUtility.blur(this);
 	}
 
 	/**
 	 * Triggers a focus event.
 	 */
 	public focus(): void {
-		if (this.ownerDocument['_activeElement'] === this || !this.isConnected) {
-			return;
-		}
-
-		if (this.ownerDocument['_activeElement'] !== null) {
-			this.ownerDocument['_activeElement'].blur();
-		}
-
-		this.ownerDocument['_activeElement'] = this;
-
-		this.dispatchEvent(
-			new FocusEvent('focus', {
-				bubbles: false,
-				composed: true
-			})
-		);
-		this.dispatchEvent(
-			new FocusEvent('focusin', {
-				bubbles: true,
-				composed: true
-			})
-		);
+		HTMLElementUtility.focus(this);
 	}
 
 	/**
diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElementUtility.ts b/packages/happy-dom/src/nodes/html-element/HTMLElementUtility.ts
new file mode 100644
index 00000000..18660d11
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-element/HTMLElementUtility.ts
@@ -0,0 +1,64 @@
+import FocusEvent from '../../event/events/FocusEvent';
+import IHTMLElement from '../html-element/IHTMLElement';
+import ISVGElement from '../svg-element/ISVGElement';
+
+/**
+ * HTMLElement utility.
+ */
+export default class HTMLElementUtility {
+	/**
+	 * Triggers a blur event.
+	 *
+	 * @param element Element.
+	 */
+	public static blur(element: IHTMLElement | ISVGElement): void {
+		if (element.ownerDocument['_activeElement'] !== element || !element.isConnected) {
+			return;
+		}
+
+		element.ownerDocument['_activeElement'] = null;
+
+		element.dispatchEvent(
+			new FocusEvent('blur', {
+				bubbles: false,
+				composed: true
+			})
+		);
+		element.dispatchEvent(
+			new FocusEvent('focusout', {
+				bubbles: true,
+				composed: true
+			})
+		);
+	}
+
+	/**
+	 * Triggers a focus event.
+	 *
+	 * @param element Element.
+	 */
+	public static focus(element: IHTMLElement | ISVGElement): void {
+		if (element.ownerDocument['_activeElement'] === element || !element.isConnected) {
+			return;
+		}
+
+		if (element.ownerDocument['_activeElement'] !== null) {
+			element.ownerDocument['_activeElement'].blur();
+		}
+
+		element.ownerDocument['_activeElement'] = element;
+
+		element.dispatchEvent(
+			new FocusEvent('focus', {
+				bubbles: false,
+				composed: true
+			})
+		);
+		element.dispatchEvent(
+			new FocusEvent('focusin', {
+				bubbles: true,
+				composed: true
+			})
+		);
+	}
+}
diff --git a/packages/happy-dom/src/nodes/svg-element/ISVGElement.ts b/packages/happy-dom/src/nodes/svg-element/ISVGElement.ts
index 70a54b0f..e3ca6545 100644
--- a/packages/happy-dom/src/nodes/svg-element/ISVGElement.ts
+++ b/packages/happy-dom/src/nodes/svg-element/ISVGElement.ts
@@ -13,6 +13,7 @@ export default interface ISVGElement extends IElement {
 	readonly ownerSVGElement: ISVGSVGElement;
 	readonly dataset: { [key: string]: string };
 	readonly style: CSSStyleDeclaration;
+	tabIndex: number;
 
 	// Events
 	onabort: (event: Event) => void | null;
@@ -21,4 +22,14 @@ export default interface ISVGElement extends IElement {
 	onresize: (event: Event) => void | null;
 	onscroll: (event: Event) => void | null;
 	onunload: (event: Event) => void | null;
+
+	/**
+	 * Triggers a blur event.
+	 */
+	blur(): void;
+
+	/**
+	 * Triggers a focus event.
+	 */
+	focus(): void;
 }
diff --git a/packages/happy-dom/src/nodes/svg-element/SVGElement.ts b/packages/happy-dom/src/nodes/svg-element/SVGElement.ts
index 7753ba87..2a23ede9 100644
--- a/packages/happy-dom/src/nodes/svg-element/SVGElement.ts
+++ b/packages/happy-dom/src/nodes/svg-element/SVGElement.ts
@@ -5,6 +5,7 @@ import ISVGSVGElement from './ISVGSVGElement';
 import IAttr from '../attr/IAttr';
 import Event from '../../event/Event';
 import Dataset from '../element/Dataset';
+import HTMLElementUtility from '../html-element/HTMLElementUtility';
 
 /**
  * SVG Element.
@@ -21,6 +22,7 @@ export default class SVGElement extends Element implements ISVGElement {
 	public onscroll: (event: Event) => void | null = null;
 	public onunload: (event: Event) => void | null = null;
 
+	// Private properties
 	private _style: CSSStyleDeclaration = null;
 	private _dataset: Dataset = null;
 
@@ -71,6 +73,43 @@ export default class SVGElement extends Element implements ISVGElement {
 		return this._style;
 	}
 
+	/**
+	 * Returns tab index.
+	 *
+	 * @returns Tab index.
+	 */
+	public get tabIndex(): number {
+		const tabIndex = this.getAttribute('tabindex');
+		return tabIndex !== null ? Number(tabIndex) : -1;
+	}
+
+	/**
+	 * Returns tab index.
+	 *
+	 * @param tabIndex Tab index.
+	 */
+	public set tabIndex(tabIndex: number) {
+		if (tabIndex === -1) {
+			this.removeAttribute('tabindex');
+		} else {
+			this.setAttribute('tabindex', String(tabIndex));
+		}
+	}
+
+	/**
+	 * Triggers a blur event.
+	 */
+	public blur(): void {
+		HTMLElementUtility.blur(this);
+	}
+
+	/**
+	 * Triggers a focus event.
+	 */
+	public focus(): void {
+		HTMLElementUtility.focus(this);
+	}
+
 	/**
 	 * @override
 	 */


Test patch:
diff --git a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
index 4a60af94..6d8e6817 100644
--- a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
+++ b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
@@ -1,6 +1,7 @@
 import PointerEvent from '../../../src/event/events/PointerEvent';
 import IDocument from '../../../src/nodes/document/IDocument';
 import HTMLElement from '../../../src/nodes/html-element/HTMLElement';
+import HTMLElementUtility from '../../../src/nodes/html-element/HTMLElementUtility';
 import IHTMLElement from '../../../src/nodes/html-element/IHTMLElement';
 import IWindow from '../../../src/window/IWindow';
 import Window from '../../../src/window/Window';
@@ -391,141 +392,30 @@ describe('HTMLElement', () => {
 	});
 
 	describe('blur()', () => {
-		it('Dispatches "blur" and "focusout" event.', () => {
-			let triggeredBlurEvent = null;
-			let triggeredFocusOutEvent = null;
+		it('Calls HTMLElementUtility.blur().', () => {
+			let blurredElement: IHTMLElement | null = null;
 
-			document.body.appendChild(element);
-
-			element.addEventListener('blur', (event) => {
-				triggeredBlurEvent = event;
-			});
-
-			element.addEventListener('focusout', (event) => {
-				triggeredFocusOutEvent = event;
-			});
-
-			element.focus();
-			element.blur();
+			jest
+				.spyOn(HTMLElementUtility, 'blur')
+				.mockImplementation((element: IHTMLElement) => (blurredElement = element));
 
-			expect(triggeredBlurEvent.type).toBe('blur');
-			expect(triggeredBlurEvent.bubbles).toBe(false);
-			expect(triggeredBlurEvent.composed).toBe(true);
-			expect(triggeredBlurEvent.target === element).toBe(true);
-
-			expect(triggeredFocusOutEvent.type).toBe('focusout');
-			expect(triggeredFocusOutEvent.bubbles).toBe(true);
-			expect(triggeredFocusOutEvent.composed).toBe(true);
-			expect(triggeredFocusOutEvent.target === element).toBe(true);
-
-			expect(document.activeElement === document.body).toBe(true);
-		});
-
-		it('Does not dispatch "blur" event if not connected to the DOM.', () => {
-			let triggeredEvent = null;
-
-			element.addEventListener('blur', (event) => {
-				triggeredEvent = event;
-			});
-
-			element.focus();
 			element.blur();
 
-			expect(triggeredEvent).toBe(null);
-		});
-
-		it('Does not dispatch "blur" event if it is not in focus.', () => {
-			let triggeredEvent = null;
-
-			document.body.appendChild(element);
-
-			element.addEventListener('blur', (event) => {
-				triggeredEvent = event;
-			});
-
-			element.blur();
-
-			expect(triggeredEvent).toBe(null);
+			expect(blurredElement === element).toBe(true);
 		});
 	});
 
 	describe('focus()', () => {
-		it('Dispatches "focus" and "focusin" event.', () => {
-			let triggeredFocusEvent = null;
-			let triggeredFocusInEvent = null;
-
-			document.body.appendChild(element);
+		it('Calls HTMLElementUtility.focus().', () => {
+			let focusedElement: IHTMLElement | null = null;
 
-			element.addEventListener('focus', (event) => {
-				triggeredFocusEvent = event;
-			});
-
-			element.addEventListener('focusin', (event) => {
-				triggeredFocusInEvent = event;
-			});
+			jest
+				.spyOn(HTMLElementUtility, 'focus')
+				.mockImplementation((element: IHTMLElement) => (focusedElement = element));
 
 			element.focus();
 
-			expect(triggeredFocusEvent.type).toBe('focus');
-			expect(triggeredFocusEvent.bubbles).toBe(false);
-			expect(triggeredFocusEvent.composed).toBe(true);
-			expect(triggeredFocusEvent.target === element).toBe(true);
-
-			expect(triggeredFocusInEvent.type).toBe('focusin');
-			expect(triggeredFocusInEvent.bubbles).toBe(true);
-			expect(triggeredFocusInEvent.composed).toBe(true);
-			expect(triggeredFocusInEvent.target === element).toBe(true);
-
-			expect(document.activeElement === element).toBe(true);
-		});
-
-		it('Does not dispatch "focus" event if not connected to the DOM.', () => {
-			let triggeredEvent = null;
-
-			element.addEventListener('focus', (event) => {
-				triggeredEvent = event;
-			});
-
-			element.focus();
-
-			expect(triggeredEvent).toBe(null);
-		});
-
-		it('Does not dispatch "focus" event if it is already focused.', () => {
-			let triggeredEvent = null;
-
-			document.body.appendChild(element);
-
-			element.focus();
-
-			element.addEventListener('focus', (event) => {
-				triggeredEvent = event;
-			});
-
-			element.focus();
-
-			expect(triggeredEvent).toBe(null);
-		});
-
-		it('Dispatches "blur" event on the previously focused element.', () => {
-			const previousElement = <HTMLElement>document.createElement('div');
-			let triggeredEvent = null;
-
-			document.body.appendChild(element);
-			document.body.appendChild(previousElement);
-
-			previousElement.focus();
-
-			previousElement.addEventListener('blur', (event) => {
-				triggeredEvent = event;
-			});
-
-			element.focus();
-
-			expect(triggeredEvent.type).toBe('blur');
-			expect(triggeredEvent.bubbles).toBe(false);
-			expect(triggeredEvent.composed).toBe(true);
-			expect(triggeredEvent.target === previousElement).toBe(true);
+			expect(focusedElement === element).toBe(true);
 		});
 	});
 
diff --git a/packages/happy-dom/test/nodes/html-element/HTMLElementUtility.test.ts b/packages/happy-dom/test/nodes/html-element/HTMLElementUtility.test.ts
new file mode 100644
index 00000000..4a86eab5
--- /dev/null
+++ b/packages/happy-dom/test/nodes/html-element/HTMLElementUtility.test.ts
@@ -0,0 +1,193 @@
+import { ISVGElement } from '../../../src';
+import IDocument from '../../../src/nodes/document/IDocument';
+import IHTMLElement from '../../../src/nodes/html-element/IHTMLElement';
+import IWindow from '../../../src/window/IWindow';
+import Window from '../../../src/window/Window';
+
+describe('HTMLElementUtility', () => {
+	let window: IWindow = null;
+	let document: IDocument = null;
+
+	beforeEach(() => {
+		window = new Window();
+		document = window.document;
+	});
+
+	afterEach(() => {
+		jest.restoreAllMocks();
+	});
+
+	describe('blur()', () => {
+		it('Dispatches "blur" and "focusout" event.', () => {
+			for (const element of [
+				<IHTMLElement>document.createElement('div'),
+				<ISVGElement>document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+			]) {
+				let triggeredBlurEvent = null;
+				let triggeredFocusOutEvent = null;
+
+				document.body.appendChild(element);
+
+				element.addEventListener('blur', (event) => {
+					triggeredBlurEvent = event;
+				});
+
+				element.addEventListener('focusout', (event) => {
+					triggeredFocusOutEvent = event;
+				});
+
+				element.focus();
+				element.blur();
+
+				expect(triggeredBlurEvent.type).toBe('blur');
+				expect(triggeredBlurEvent.bubbles).toBe(false);
+				expect(triggeredBlurEvent.composed).toBe(true);
+				expect(triggeredBlurEvent.target === element).toBe(true);
+
+				expect(triggeredFocusOutEvent.type).toBe('focusout');
+				expect(triggeredFocusOutEvent.bubbles).toBe(true);
+				expect(triggeredFocusOutEvent.composed).toBe(true);
+				expect(triggeredFocusOutEvent.target === element).toBe(true);
+
+				expect(document.activeElement === document.body).toBe(true);
+			}
+		});
+
+		it('Does not dispatch "blur" event if not connected to the DOM.', () => {
+			for (const element of [
+				<IHTMLElement>document.createElement('div'),
+				<ISVGElement>document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+			]) {
+				let triggeredEvent = null;
+
+				element.addEventListener('blur', (event) => {
+					triggeredEvent = event;
+				});
+
+				element.focus();
+				element.blur();
+
+				expect(triggeredEvent).toBe(null);
+			}
+		});
+
+		it('Does not dispatch "blur" event if it is not in focus.', () => {
+			for (const element of [
+				<IHTMLElement>document.createElement('div'),
+				<ISVGElement>document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+			]) {
+				let triggeredEvent = null;
+
+				document.body.appendChild(element);
+
+				element.addEventListener('blur', (event) => {
+					triggeredEvent = event;
+				});
+
+				element.blur();
+
+				expect(triggeredEvent).toBe(null);
+			}
+		});
+	});
+
+	describe('focus()', () => {
+		it('Dispatches "focus" and "focusin" event.', () => {
+			for (const element of [
+				<IHTMLElement>document.createElement('div'),
+				<ISVGElement>document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+			]) {
+				let triggeredFocusEvent = null;
+				let triggeredFocusInEvent = null;
+
+				document.body.appendChild(element);
+
+				element.addEventListener('focus', (event) => {
+					triggeredFocusEvent = event;
+				});
+
+				element.addEventListener('focusin', (event) => {
+					triggeredFocusInEvent = event;
+				});
+
+				element.focus();
+
+				expect(triggeredFocusEvent.type).toBe('focus');
+				expect(triggeredFocusEvent.bubbles).toBe(false);
+				expect(triggeredFocusEvent.composed).toBe(true);
+				expect(triggeredFocusEvent.target === element).toBe(true);
+
+				expect(triggeredFocusInEvent.type).toBe('focusin');
+				expect(triggeredFocusInEvent.bubbles).toBe(true);
+				expect(triggeredFocusInEvent.composed).toBe(true);
+				expect(triggeredFocusInEvent.target === element).toBe(true);
+
+				expect(document.activeElement === element).toBe(true);
+			}
+		});
+
+		it('Does not dispatch "focus" event if not connected to the DOM.', () => {
+			for (const element of [
+				<IHTMLElement>document.createElement('div'),
+				<ISVGElement>document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+			]) {
+				let triggeredEvent = null;
+
+				element.addEventListener('focus', (event) => {
+					triggeredEvent = event;
+				});
+
+				element.focus();
+
+				expect(triggeredEvent).toBe(null);
+			}
+		});
+
+		it('Does not dispatch "focus" event if it is already focused.', () => {
+			for (const element of [
+				<IHTMLElement>document.createElement('div'),
+				<ISVGElement>document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+			]) {
+				let triggeredEvent = null;
+
+				document.body.appendChild(element);
+
+				element.focus();
+
+				element.addEventListener('focus', (event) => {
+					triggeredEvent = event;
+				});
+
+				element.focus();
+
+				expect(triggeredEvent).toBe(null);
+			}
+		});
+
+		it('Dispatches "blur" event on the previously focused element.', () => {
+			for (const element of [
+				<IHTMLElement>document.createElement('div'),
+				<ISVGElement>document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+			]) {
+				const previousElement = <IHTMLElement>document.createElement('div');
+				let triggeredEvent = null;
+
+				document.body.appendChild(element);
+				document.body.appendChild(previousElement);
+
+				previousElement.focus();
+
+				previousElement.addEventListener('blur', (event) => {
+					triggeredEvent = event;
+				});
+
+				element.focus();
+
+				expect(triggeredEvent.type).toBe('blur');
+				expect(triggeredEvent.bubbles).toBe(false);
+				expect(triggeredEvent.composed).toBe(true);
+				expect(triggeredEvent.target === previousElement).toBe(true);
+			}
+		});
+	});
+});
diff --git a/packages/happy-dom/test/nodes/svg-element/SVGElement.test.ts b/packages/happy-dom/test/nodes/svg-element/SVGElement.test.ts
index 35aeb7ee..ad27f743 100644
--- a/packages/happy-dom/test/nodes/svg-element/SVGElement.test.ts
+++ b/packages/happy-dom/test/nodes/svg-element/SVGElement.test.ts
@@ -1,20 +1,21 @@
 import Window from '../../../src/window/Window';
 import Document from '../../../src/nodes/document/Document';
-import SVGSVGElement from '../../../src/nodes/svg-element/SVGSVGElement';
+import ISVGSVGElement from '../../../src/nodes/svg-element/ISVGSVGElement';
 import NamespaceURI from '../../../src/config/NamespaceURI';
-import SVGElement from '../../../src/nodes/svg-element/SVGElement';
+import ISVGElement from '../../../src/nodes/svg-element/ISVGElement';
+import HTMLElementUtility from '../../../src/nodes/html-element/HTMLElementUtility';
 
 describe('SVGElement', () => {
 	let window: Window;
 	let document: Document;
-	let element: SVGSVGElement;
-	let line: SVGElement;
+	let element: ISVGSVGElement;
+	let line: ISVGElement;
 
 	beforeEach(() => {
 		window = new Window();
 		document = window.document;
-		element = <SVGSVGElement>document.createElementNS(NamespaceURI.svg, 'svg');
-		line = <SVGSVGElement>document.createElementNS(NamespaceURI.svg, 'line');
+		element = <ISVGSVGElement>document.createElementNS(NamespaceURI.svg, 'svg');
+		line = <ISVGElement>document.createElementNS(NamespaceURI.svg, 'line');
 	});
 
 	describe('get ownerSVGElement()', () => {
@@ -28,38 +29,86 @@ describe('SVGElement', () => {
 			const ownerSVG = line.ownerSVGElement;
 			expect(ownerSVG).toBe(null);
 		});
+	});
+
+	describe('get dataset()', () => {
+		it('Returns a Proxy behaving like an object that can add, remove, set and get element attributes prefixed with "data-".', () => {
+			element.setAttribute('test-alpha', 'value1');
+			element.setAttribute('data-test-alpha', 'value2');
+			element.setAttribute('test-beta', 'value3');
+			element.setAttribute('data-test-beta', 'value4');
+
+			const dataset = element.dataset;
+
+			expect(dataset).toBe(element.dataset);
+			expect(Object.keys(dataset)).toEqual(['testAlpha', 'testBeta']);
+			expect(Object.values(dataset)).toEqual(['value2', 'value4']);
+
+			dataset.testGamma = 'value5';
+
+			expect(element.getAttribute('data-test-gamma')).toBe('value5');
+			expect(Object.keys(dataset)).toEqual(['testAlpha', 'testBeta', 'testGamma']);
+			expect(Object.values(dataset)).toEqual(['value2', 'value4', 'value5']);
 
-		describe('get dataset()', () => {
-			it('Returns a Proxy behaving like an object that can add, remove, set and get element attributes prefixed with "data-".', () => {
-				element.setAttribute('test-alpha', 'value1');
-				element.setAttribute('data-test-alpha', 'value2');
-				element.setAttribute('test-beta', 'value3');
-				element.setAttribute('data-test-beta', 'value4');
+			element.setAttribute('data-test-delta', 'value6');
 
-				const dataset = element.dataset;
+			expect(dataset.testDelta).toBe('value6');
+			expect(Object.keys(dataset)).toEqual(['testAlpha', 'testBeta', 'testGamma', 'testDelta']);
+			expect(Object.values(dataset)).toEqual(['value2', 'value4', 'value5', 'value6']);
 
-				expect(dataset).toBe(element.dataset);
-				expect(Object.keys(dataset)).toEqual(['testAlpha', 'testBeta']);
-				expect(Object.values(dataset)).toEqual(['value2', 'value4']);
+			delete dataset.testDelta;
 
-				dataset.testGamma = 'value5';
+			expect(element.getAttribute('data-test-delta')).toBe(null);
+			expect(Object.keys(dataset)).toEqual(['testAlpha', 'testBeta', 'testGamma']);
+			expect(Object.values(dataset)).toEqual(['value2', 'value4', 'value5']);
+		});
+	});
+
+	describe('get tabIndex()', () => {
+		it('Returns the attribute "tabindex" as a number.', () => {
+			element.setAttribute('tabindex', '5');
+			expect(element.tabIndex).toBe(5);
+		});
+	});
 
-				expect(element.getAttribute('data-test-gamma')).toBe('value5');
-				expect(Object.keys(dataset)).toEqual(['testAlpha', 'testBeta', 'testGamma']);
-				expect(Object.values(dataset)).toEqual(['value2', 'value4', 'value5']);
+	describe('set tabIndex()', () => {
+		it('Sets the attribute "tabindex".', () => {
+			element.tabIndex = 5;
+			expect(element.getAttribute('tabindex')).toBe('5');
+		});
+
+		it('Removes the attribute "tabindex" when set to "-1".', () => {
+			element.tabIndex = 5;
+			element.tabIndex = -1;
+			expect(element.getAttribute('tabindex')).toBe(null);
+		});
+	});
+
+	describe('blur()', () => {
+		it('Calls HTMLElementUtility.blur().', () => {
+			let blurredElement: ISVGElement | null = null;
+
+			jest
+				.spyOn(HTMLElementUtility, 'blur')
+				.mockImplementation((element: ISVGElement) => (blurredElement = element));
+
+			element.blur();
+
+			expect(blurredElement === element).toBe(true);
+		});
+	});
 
-				element.setAttribute('data-test-delta', 'value6');
+	describe('focus()', () => {
+		it('Calls HTMLElementUtility.focus().', () => {
+			let focusedElement: ISVGElement | null = null;
 
-				expect(dataset.testDelta).toBe('value6');
-				expect(Object.keys(dataset)).toEqual(['testAlpha', 'testBeta', 'testGamma', 'testDelta']);
-				expect(Object.values(dataset)).toEqual(['value2', 'value4', 'value5', 'value6']);
+			jest
+				.spyOn(HTMLElementUtility, 'focus')
+				.mockImplementation((element: ISVGElement) => (focusedElement = element));
 
-				delete dataset.testDelta;
+			element.focus();
 
-				expect(element.getAttribute('data-test-delta')).toBe(null);
-				expect(Object.keys(dataset)).toEqual(['testAlpha', 'testBeta', 'testGamma']);
-				expect(Object.values(dataset)).toEqual(['value2', 'value4', 'value5']);
-			});
+			expect(focusedElement === element).toBe(true);
 		});
 	});
 });