Repository: capricorn86/happy-dom (ts)

Issue:
`HTMLElement.style` cannot be set directly
**Describe the bug**

`HTMLElement.style` cannot be set, i.e `element.style = 'display: none'` doesn't work in happy-dom though it works in browsers (even if the `style` property is readonly according to the spec).

https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style#setting_styles

> While this property is considered read-only, it is possible to set an inline style by assigning a string directly to the style property. In this case the string is forwarded to [CSSStyleDeclaration.cssText](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/cssText). Using style in this manner will completely overwrite all inline styles on the element.

**To Reproduce**

```ts
const { Window } = require('happy-dom');

const window = new Window();
const document = window.document;
const button = document.createElement('button');
button.style = 'display: none';

console.log(button.outerHTML); // "<button></button>"
```

**Expected behavior**

```ts
const button = document.createElement('button');
button.style = 'display: none';
console.log(button.outerHTML); // should display "<button style="display: none"></button>"
```

**Device:**
 - Version 7.6.0
 - Node 16.17.1


Pull request description:
#628@patch: Handle style setter in HTMLElement.
This PR adds a setter on `HTMLElement.style` the reproduce the behaviour of DOM implementations, as explained in #628. 

This also fixes another difference between happy-dom and browser implementations: setting `null` or `''` as `style` (or in `cssText`)  shouldn't remove the `style` attribute.

```ts
element.style.cssText = null;
console.log(element.getAttribute('style')); // displays '', not null
```

Since TypeScript forces a value returned by a getter to be assignable to a setter (see https://github.com/microsoft/TypeScript/pull/42425), the `style` setter accepts a `CSSStyleDeclaration` but then resets the `cssText`, as implemented in some browsers.

Closes #628 

Patch:
diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts
index af58ea6a..22547789 100644
--- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts
+++ b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts
@@ -77,21 +77,17 @@ export default abstract class AbstractCSSStyleDeclaration {
 
 		if (this._ownerElement) {
 			const style = new CSSStyleDeclarationPropertyManager({ cssText });
-			if (!style.size()) {
-				delete this._ownerElement['_attributes']['style'];
-			} else {
-				if (!this._ownerElement['_attributes']['style']) {
-					Attr._ownerDocument = this._ownerElement.ownerDocument;
-					this._ownerElement['_attributes']['style'] = new Attr();
-					this._ownerElement['_attributes']['style'].name = 'style';
-				}
-
-				if (this._ownerElement.isConnected) {
-					this._ownerElement.ownerDocument['_cacheID']++;
-				}
+			if (!this._ownerElement['_attributes']['style']) {
+				Attr._ownerDocument = this._ownerElement.ownerDocument;
+				this._ownerElement['_attributes']['style'] = new Attr();
+				this._ownerElement['_attributes']['style'].name = 'style';
+			}
 
-				this._ownerElement['_attributes']['style'].value = style.toString();
+			if (this._ownerElement.isConnected) {
+				this._ownerElement.ownerDocument['_cacheID']++;
 			}
+
+			this._ownerElement['_attributes']['style'].value = style.toString();
 		} else {
 			this._style = new CSSStyleDeclarationPropertyManager({ cssText });
 		}
diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
index beb210e7..bb1a9387 100644
--- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
+++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
@@ -200,6 +200,16 @@ export default class HTMLElement extends Element implements IHTMLElement {
 		return this._style;
 	}
 
+	/**
+	 * Sets style.
+	 *
+	 * @param cssText Style as text.
+	 * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style#setting_styles
+	 */
+	public set style(cssText: string | CSSStyleDeclaration) {
+		this.style.cssText = typeof cssText === 'string' ? <string>cssText : '';
+	}
+
 	/**
 	 * Returns data set.
 	 *
diff --git a/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts b/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts
index f79f8b01..35ab764d 100644
--- a/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts
+++ b/packages/happy-dom/src/nodes/html-element/IHTMLElement.ts
@@ -9,7 +9,6 @@ import IElement from '../element/IElement';
  * https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.
  */
 export default interface IHTMLElement extends IElement {
-	style: CSSStyleDeclaration;
 	dataset: { [key: string]: string };
 	tabIndex: number;
 	offsetHeight: number;
@@ -48,6 +47,9 @@ export default interface IHTMLElement extends IElement {
 	ontransitionrun: (event: Event) => void | null;
 	ontransitionstart: (event: Event) => void | null;
 
+	get style(): CSSStyleDeclaration;
+	set style(cssText: CSSStyleDeclaration | string);
+
 	/**
 	 * Triggers a click event.
 	 */


Test patch:
diff --git a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts
index 1d3a1302..ddf4b572 100644
--- a/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts
+++ b/packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts
@@ -1978,7 +1978,7 @@ describe('CSSStyleDeclaration', () => {
 
 			declaration.cssText = '';
 
-			expect(element.getAttribute('style')).toBe(null);
+			expect(element.getAttribute('style')).toBe('');
 		});
 	});
 
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 9d205331..b8cb6985 100644
--- a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
+++ b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
@@ -239,6 +239,34 @@ describe('HTMLElement', () => {
 		});
 	});
 
+	describe('set style()', () => {
+		it('Sets the value of the style.cssText property.', () => {
+			element.style = 'border-radius: 2px; padding: 2px;';
+
+			expect(element.style.cssText).toEqual('border-radius: 2px; padding: 2px;');
+			expect(element.style.borderRadius).toEqual('2px');
+			expect(element.style.padding).toEqual('2px');
+			expect(element.getAttribute('style')).toEqual('border-radius: 2px; padding: 2px;');
+			expect(element.outerHTML).toEqual('<div style="border-radius: 2px; padding: 2px;"></div>');
+
+			element.style = '';
+
+			expect(element.style.cssText).toEqual('');
+			expect(element.style.borderRadius).toEqual('');
+			expect(element.style.padding).toEqual('');
+			expect(element.getAttribute('style')).toEqual('');
+			expect(element.outerHTML).toEqual('<div style=""></div>');
+
+			element.style = null;
+
+			expect(element.style.cssText).toEqual('');
+			expect(element.style.borderRadius).toEqual('');
+			expect(element.style.padding).toEqual('');
+			expect(element.getAttribute('style')).toEqual('');
+			expect(element.outerHTML).toEqual('<div style=""></div>');
+		});
+	});
+
 	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');