Repository: capricorn86/happy-dom (ts)

Issue:
toggleAttribute is not a function
**Describe the bug**
When attempting to access function `toggleAttribute` after `.querySelector`, an error is displayed:

```
container.toggleAttribute('disabled');
          ^

TypeError: container.toggleAttribute is not a function
    at file:///D:/Projects/happ-dom-repro1/index.js:9:11
    at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:530:24)
    at async loadESM (node:internal/process/esm_loader:91:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12)

Node.js v18.12.0
```

According to MDN, this function should be supported:
https://developer.mozilla.org/en-US/docs/Web/API/Element/toggleAttribute

**To Reproduce**
Run this code with `node index.js` after basing it off the basic example:

```js
import { Window } from 'happy-dom';

const window = new Window();
const document = window.document;

document.body.innerHTML = '<div class="container"></div>';

const container = document.querySelector('.container');
container.toggleAttribute('disabled');
```

**Expected behavior**
Attributes should be toggle on/off as expected of the spec from MDN.

**Device:**
 - OS: Windows 11


Pull request description:
#724@minor: Add toggleAttribute.
close #724 

Patch:
diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts
index 70063c6e..a83601d1 100644
--- a/packages/happy-dom/src/nodes/element/Element.ts
+++ b/packages/happy-dom/src/nodes/element/Element.ts
@@ -591,6 +591,30 @@ export default class Element extends Node implements IElement {
 		return null;
 	}
 
+	/**
+	 * Toggle an attribute.
+	 * Returns `true` if attribute name is eventually present, and `false` otherwise.
+	 *
+	 * @param name A DOMString specifying the name of the attribute to be toggled.
+	 * @param force A boolean value to determine whether the attribute should be added or removed, no matter whether the attribute is present or not at the moment.
+	 */
+	public toggleAttribute(name: string, force?: boolean): boolean {
+		name = name.toLowerCase();
+		const attribute = this.getAttributeNode(name);
+		if (attribute) {
+			if (force === true) {
+				return true;
+			}
+			this.removeAttributeNode(attribute);
+			return false;
+		}
+		if (force === false) {
+			return false;
+		}
+		this.setAttribute(name, '');
+		return true;
+	}
+
 	/**
 	 * Returns namespace attribute value.
 	 *
diff --git a/packages/happy-dom/src/nodes/element/IElement.ts b/packages/happy-dom/src/nodes/element/IElement.ts
index b3fc6ff5..d849b615 100644
--- a/packages/happy-dom/src/nodes/element/IElement.ts
+++ b/packages/happy-dom/src/nodes/element/IElement.ts
@@ -168,6 +168,14 @@ export default interface IElement extends IChildNode, INonDocumentTypeChildNode,
 	 */
 	removeAttributeNS(namespace: string, localName: string): void;
 
+	/**
+	 * Toggle an attribute.
+	 *
+	 * @param name A DOMString specifying the name of the attribute to be toggled.
+	 * @param force A boolean value to determine whether the attribute should be added or removed, no matter whether the attribute is present or not at the moment.
+	 */
+	toggleAttribute(name: string, force?: boolean): boolean;
+
 	/**
 	 * Attaches a shadow root.
 	 *


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 36fa3fa0..2590b438 100644
--- a/packages/happy-dom/test/nodes/element/Element.test.ts
+++ b/packages/happy-dom/test/nodes/element/Element.test.ts
@@ -1232,6 +1232,21 @@ describe('Element', () => {
 		});
 	});
 
+	describe('toggleAttribute()', () => {
+		it('Toggles an attribute.', () => {
+			element.toggleAttribute('key1');
+			expect(element.hasAttribute('key1')).toBe(true);
+			element.toggleAttribute('key1');
+			expect(element.hasAttribute('key1')).toBe(false);
+			element.toggleAttribute('key1', true);
+			expect(element.hasAttribute('key1')).toBe(true);
+			element.toggleAttribute('key1', true);
+			expect(element.hasAttribute('key1')).toBe(true);
+			element.toggleAttribute('key1', false);
+			expect(element.hasAttribute('key1')).toBe(false);
+		});
+	});
+
 	describe('attachShadow()', () => {
 		it('Creates a new open ShadowRoot node and sets it to the "shadowRoot" property.', () => {
 			element.attachShadow({ mode: 'open' });