Repository: capricorn86/happy-dom
Pull request #930 (2023-05-22 22:35:30)

#929@minor: Adds support for disabling the simulation of rendering wh…
…en calculating computed style. The rendering process converts units such as rem, em and cm to pixels, but it is very limited.


Patch:
diff --git a/packages/happy-dom/README.md b/packages/happy-dom/README.md
index 3a34e699..6b190703 100644
--- a/packages/happy-dom/README.md
+++ b/packages/happy-dom/README.md
@@ -258,6 +258,7 @@ const window = new Window({
 		disableJavaScriptEvaluation: true,
 		disableCSSFileLoading: true,
 		disableIframePageLoading: true,
+		disableComputedStyleRendering: true,
 		enableFileSystemHttpRequests: true,
         device: {
             mediaType: 'print',
@@ -276,6 +277,7 @@ window.happyDOM.settings.disableJavaScriptFileLoading = true;
 window.happyDOM.settings.disableJavaScriptEvaluation = true;
 window.happyDOM.settings.disableCSSFileLoading = true;
 window.happyDOM.settings.disableIframePageLoading = true;
+window.happyDOM.settings.disableComputedStyleRendering = true;
 window.happyDOM.settings.enableFileSystemHttpRequests = true;
 window.happyDOM.settings.device.mediaType = 'print';
 window.happyDOM.settings.device.prefersColorScheme = 'dark';
@@ -297,6 +299,9 @@ Set it to "true" to disable CSS file loading in HTMLLinkElement. Defaults to "fa
 
 Set it to "true" to disable page loading in HTMLIFrameElement. Defaults to "false".
 
+**disableComputedStyleRendering**
+Set it to "true" to disable the simulation of rendering when calculating computed style. The rendering process will convert values such as "rem", "em", "cm" etc. to pixels. However, it is currently very limited and will therefore not give the same result as the browser. Defaults to "false".
+
 **enableFileSystemHttpRequests**
 
 Set it to "true" to enable file system HTTP requests using XMLHttpRequest. Defaults to "false".
diff --git a/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts b/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts
index edbc726a..e4ddb20f 100644
--- a/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts
+++ b/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts
@@ -232,7 +232,7 @@ export default class CSSStyleDeclarationElementStyle {
 					parentFontSize,
 
 					// TODO: Only "font-size" is supported when using percentage values. Add support for other properties.
-					parentSize: name === 'font-size' ? parentFontSize : 0
+					parentSize: name === 'font-size' ? parentFontSize : null
 				});
 			}
 		}
@@ -335,8 +335,12 @@ export default class CSSStyleDeclarationElementStyle {
 		value: string;
 		rootFontSize: string | number;
 		parentFontSize: string | number;
-		parentSize: string | number;
+		parentSize: string | number | null;
 	}): string {
+		if (this.element.ownerDocument.defaultView.happyDOM.settings.disableComputedStyleRendering) {
+			return options.value;
+		}
+
 		const regexp = new RegExp(CSS_MEASUREMENT_REGEXP);
 		let newValue = options.value;
 		let match;
diff --git a/packages/happy-dom/src/match-media/MediaQueryItem.ts b/packages/happy-dom/src/match-media/MediaQueryItem.ts
index 95d7a841..f4fc6175 100644
--- a/packages/happy-dom/src/match-media/MediaQueryItem.ts
+++ b/packages/happy-dom/src/match-media/MediaQueryItem.ts
@@ -313,7 +313,7 @@ export default class MediaQueryItem {
 	 * @returns Value in pixels.
 	 */
 	private toPixels(value: string): number | null {
-		if (value.endsWith('em')) {
+		if (!this.ownerWindow.happyDOM.settings.disableComputedStyleRendering && value.endsWith('em')) {
 			this.rootFontSize =
 				this.rootFontSize ||
 				parseFloat(
diff --git a/packages/happy-dom/src/window/IHappyDOMOptions.ts b/packages/happy-dom/src/window/IHappyDOMOptions.ts
index f8da6776..7ea7e2a7 100644
--- a/packages/happy-dom/src/window/IHappyDOMOptions.ts
+++ b/packages/happy-dom/src/window/IHappyDOMOptions.ts
@@ -10,6 +10,7 @@ export default interface IHappyDOMOptions {
 		disableJavaScriptFileLoading?: boolean;
 		disableCSSFileLoading?: boolean;
 		disableIframePageLoading?: boolean;
+		disableComputedStyleRendering?: boolean;
 		enableFileSystemHttpRequests?: boolean;
 		device?: {
 			prefersColorScheme?: string;
diff --git a/packages/happy-dom/src/window/IHappyDOMSettings.ts b/packages/happy-dom/src/window/IHappyDOMSettings.ts
index d37f4381..0eec929c 100644
--- a/packages/happy-dom/src/window/IHappyDOMSettings.ts
+++ b/packages/happy-dom/src/window/IHappyDOMSettings.ts
@@ -6,6 +6,7 @@ export default interface IHappyDOMSettings {
 	disableJavaScriptFileLoading: boolean;
 	disableCSSFileLoading: boolean;
 	disableIframePageLoading: boolean;
+	disableComputedStyleRendering: boolean;
 	enableFileSystemHttpRequests: boolean;
 	device: {
 		prefersColorScheme: string;
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index 97fb7c55..bfe5aa11 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -177,6 +177,7 @@ export default class Window extends EventTarget implements IWindow {
 			disableJavaScriptFileLoading: false,
 			disableCSSFileLoading: false,
 			disableIframePageLoading: false,
+			disableComputedStyleRendering: false,
 			enableFileSystemHttpRequests: false,
 			device: {
 				prefersColorScheme: 'light',


Test patch:
diff --git a/packages/happy-dom/test/match-media/MediaQueryList.test.ts b/packages/happy-dom/test/match-media/MediaQueryList.test.ts
index 893356f3..1d198301 100644
--- a/packages/happy-dom/test/match-media/MediaQueryList.test.ts
+++ b/packages/happy-dom/test/match-media/MediaQueryList.test.ts
@@ -485,6 +485,26 @@ describe('MediaQueryList', () => {
 				}).matches
 			).toBe(true);
 		});
+
+		it('Handles disabling computed style rendering with Window.happyDOM.settings.disableComputedStyleRendering.', () => {
+			window.document.documentElement.style.fontSize = '10px';
+
+			expect(
+				new MediaQueryList({ ownerWindow: window, media: `(max-width: ${1023 / 10}rem)` }).matches
+			).toBe(false);
+			expect(
+				new MediaQueryList({ ownerWindow: window, media: `(max-width: ${1024 / 10}rem)` }).matches
+			).toBe(true);
+
+			window.happyDOM.settings.disableComputedStyleRendering = true;
+
+			expect(
+				new MediaQueryList({ ownerWindow: window, media: `(max-width: ${1023 / 16}rem)` }).matches
+			).toBe(false);
+			expect(
+				new MediaQueryList({ ownerWindow: window, media: `(max-width: ${1024 / 16}rem)` }).matches
+			).toBe(true);
+		});
 	});
 
 	describe('addEventListener()', () => {
diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts
index 0bb8f8e6..469903d6 100644
--- a/packages/happy-dom/test/window/Window.test.ts
+++ b/packages/happy-dom/test/window/Window.test.ts
@@ -436,7 +436,7 @@ describe('Window', () => {
 			expect(computedStyle.height).toBe('150px');
 		});
 
-		it('Returns a CSSStyleDeclaration object with computed styles containing "%" measurement values converted to pixels.', () => {
+		it('Returns a CSSStyleDeclaration object with computed styles containing "%" measurement values that have not been converted, as it is not supported yet.', () => {
 			const parent = <IHTMLElement>document.createElement('div');
 			const element = <IHTMLElement>document.createElement('span');
 			const computedStyle = window.getComputedStyle(element);
@@ -457,7 +457,7 @@ describe('Window', () => {
 
 			elementStyle.innerHTML = `
                 span {
-					width: 100%;
+					width: 80%;
                     height: 10em;
                 }
 			`;
@@ -468,10 +468,48 @@ describe('Window', () => {
 			document.body.appendChild(parentStyle);
 			document.body.appendChild(parent);
 
-			expect(computedStyle.width).toBe('0px');
+			expect(computedStyle.width).toBe('80%');
 			expect(computedStyle.height).toBe('150px');
 		});
 
+		it('Returns a CSSStyleDeclaration object with computed styles containing "rem" and "em" measurement values that has not been converted to pixels if Window.happyDOM.settings.disableComputedStyleRendering is set to "true".', () => {
+			const parent = <IHTMLElement>document.createElement('div');
+			const element = <IHTMLElement>document.createElement('span');
+			const computedStyle = window.getComputedStyle(element);
+			const parentStyle = document.createElement('style');
+			const elementStyle = document.createElement('style');
+
+			window.happyDOM.setInnerWidth(1024);
+
+			parentStyle.innerHTML = `
+                html {
+                    font-size: 10px;
+                }
+
+				div {
+                    font-size: 1.5rem;
+				}
+			`;
+
+			elementStyle.innerHTML = `
+                span {
+					width: 10rem;
+                    height: 10em;
+                }
+			`;
+
+			parent.appendChild(elementStyle);
+			parent.appendChild(element);
+
+			document.body.appendChild(parentStyle);
+			document.body.appendChild(parent);
+
+			window.happyDOM.settings.disableComputedStyleRendering = true;
+
+			expect(computedStyle.width).toBe('10rem');
+			expect(computedStyle.height).toBe('10em');
+		});
+
 		for (const measurement of [
 			{ value: '100vw', result: '1024px' },
 			{ value: '100vh', result: '768px' },