Repository: capricorn86/happy-dom
Pull request #918 (2023-05-13 11:08:51)

#849@minor: Adds support for triggering an error event when attemptin…
…g to fetch a CSS file or Javascript file when it has been disabled using "window.happyDOM.settings.*".


Patch:
diff --git a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts
index 96d3d052..14e02a39 100644
--- a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts
+++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts
@@ -1,14 +1,13 @@
 import IAttr from '../attr/IAttr';
 import CSSStyleSheet from '../../css/CSSStyleSheet';
-import ResourceFetch from '../../fetch/ResourceFetch';
 import HTMLElement from '../html-element/HTMLElement';
-import Document from '../document/Document';
 import IHTMLLinkElement from './IHTMLLinkElement';
 import Event from '../../event/Event';
 import ErrorEvent from '../../event/events/ErrorEvent';
 import INode from '../../nodes/node/INode';
 import DOMTokenList from '../../dom-token-list/DOMTokenList';
 import IDOMTokenList from '../../dom-token-list/IDOMTokenList';
+import HTMLLinkElementUtility from './HTMLLinkElementUtility';
 
 /**
  * HTML Link Element.
@@ -184,51 +183,13 @@ export default class HTMLLinkElement extends HTMLElement implements IHTMLLinkEle
 	 */
 	public override setAttributeNode(attribute: IAttr): IAttr {
 		const replacedAttribute = super.setAttributeNode(attribute);
-		const rel = this.getAttribute('rel');
-		const href = this.getAttribute('href');
 
 		if (attribute.name === 'rel' && this._relList) {
 			this._relList._updateIndices();
 		}
 
-		if (
-			(attribute.name === 'rel' || attribute.name === 'href') &&
-			href !== null &&
-			rel &&
-			rel.toLowerCase() === 'stylesheet' &&
-			this.isConnected &&
-			!this.ownerDocument.defaultView.happyDOM.settings.disableCSSFileLoading
-		) {
-			(<Document>this.ownerDocument)._readyStateManager.startTask();
-			ResourceFetch.fetch(this.ownerDocument, href)
-				.then((code) => {
-					const styleSheet = new CSSStyleSheet();
-					styleSheet.replaceSync(code);
-					(<CSSStyleSheet>this.sheet) = styleSheet;
-					this.dispatchEvent(new Event('load'));
-					(<Document>this.ownerDocument)._readyStateManager.endTask();
-				})
-				.catch((error) => {
-					this.dispatchEvent(
-						new ErrorEvent('error', {
-							message: error.message,
-							error
-						})
-					);
-					this.ownerDocument.defaultView.dispatchEvent(
-						new ErrorEvent('error', {
-							message: error.message,
-							error
-						})
-					);
-					(<Document>this.ownerDocument)._readyStateManager.endTask();
-					if (
-						!this['_listeners']['error'] &&
-						!this.ownerDocument.defaultView['_listeners']['error']
-					) {
-						this.ownerDocument.defaultView.console.error(error);
-					}
-				});
+		if (attribute.name === 'rel' || attribute.name === 'href') {
+			HTMLLinkElementUtility.loadExternalStylesheet(this);
 		}
 
 		return replacedAttribute;
@@ -256,47 +217,8 @@ export default class HTMLLinkElement extends HTMLElement implements IHTMLLinkEle
 
 		super._connectToNode(parentNode);
 
-		if (
-			isParentConnected &&
-			isConnected !== isParentConnected &&
-			this._evaluateCSS &&
-			!this.ownerDocument.defaultView.happyDOM.settings.disableCSSFileLoading
-		) {
-			const href = this.getAttribute('href');
-			const rel = this.getAttribute('rel');
-
-			if (href !== null && rel && rel.toLowerCase() === 'stylesheet') {
-				(<Document>this.ownerDocument)._readyStateManager.startTask();
-				ResourceFetch.fetch(this.ownerDocument, href)
-					.then((code) => {
-						const styleSheet = new CSSStyleSheet();
-						styleSheet.replaceSync(code);
-						(<CSSStyleSheet>this.sheet) = styleSheet;
-						this.dispatchEvent(new Event('load'));
-						(<Document>this.ownerDocument)._readyStateManager.endTask();
-					})
-					.catch((error) => {
-						this.dispatchEvent(
-							new ErrorEvent('error', {
-								message: error.message,
-								error
-							})
-						);
-						this.ownerDocument.defaultView.dispatchEvent(
-							new ErrorEvent('error', {
-								message: error.message,
-								error
-							})
-						);
-						(<Document>this.ownerDocument)._readyStateManager.endTask();
-						if (
-							!this['_listeners']['error'] &&
-							!this.ownerDocument.defaultView['_listeners']['error']
-						) {
-							this.ownerDocument.defaultView.console.error(error);
-						}
-					});
-			}
+		if (isParentConnected && isConnected !== isParentConnected && this._evaluateCSS) {
+			HTMLLinkElementUtility.loadExternalStylesheet(this);
 		}
 	}
 }
diff --git a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementUtility.ts b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementUtility.ts
new file mode 100644
index 00000000..a8dc437a
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementUtility.ts
@@ -0,0 +1,82 @@
+import Document from '../document/Document';
+import Event from '../../event/Event';
+import ErrorEvent from '../../event/events/ErrorEvent';
+import ResourceFetch from '../../fetch/ResourceFetch';
+import HTMLLinkElement from './HTMLLinkElement';
+import CSSStyleSheet from '../../css/CSSStyleSheet';
+import DOMException from '../../exception/DOMException';
+import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum';
+
+/**
+ * Helper class for getting the URL relative to a Location object.
+ */
+export default class HTMLLinkElementUtility {
+	/**
+	 * Returns a URL relative to the given Location object.
+	 *
+	 * @param options Options.
+	 * @param options.element Element.
+	 * @param element
+	 */
+	public static async loadExternalStylesheet(element: HTMLLinkElement): Promise<void> {
+		const href = element.getAttribute('href');
+		const rel = element.getAttribute('rel');
+
+		if (href !== null && rel && rel.toLowerCase() === 'stylesheet' && element.isConnected) {
+			if (element.ownerDocument.defaultView.happyDOM.settings.disableCSSFileLoading) {
+				this.onError(
+					element,
+					new DOMException(
+						`Failed to load external stylesheet "${href}". CSS file loading is disabled.`,
+						DOMExceptionNameEnum.notSupportedError
+					)
+				);
+				return;
+			}
+
+			(<Document>element.ownerDocument)._readyStateManager.startTask();
+
+			let code: string;
+			try {
+				code = await ResourceFetch.fetch(element.ownerDocument, href);
+			} catch (error) {
+				this.onError(element, error);
+				return;
+			}
+
+			const styleSheet = new CSSStyleSheet();
+			styleSheet.replaceSync(code);
+			(<CSSStyleSheet>element.sheet) = styleSheet;
+			element.dispatchEvent(new Event('load'));
+			(<Document>element.ownerDocument)._readyStateManager.endTask();
+		}
+	}
+
+	/**
+	 * Triggered when an error occurs.
+	 *
+	 * @param element Element.
+	 * @param error Error.
+	 */
+	private static onError(element: HTMLLinkElement, error: Error): void {
+		element.dispatchEvent(
+			new ErrorEvent('error', {
+				message: error.message,
+				error
+			})
+		);
+		element.ownerDocument.defaultView.dispatchEvent(
+			new ErrorEvent('error', {
+				message: error.message,
+				error
+			})
+		);
+		(<Document>element.ownerDocument)._readyStateManager.endTask();
+		if (
+			!element['_listeners']['error'] &&
+			!element.ownerDocument.defaultView['_listeners']['error']
+		) {
+			element.ownerDocument.defaultView.console.error(error);
+		}
+	}
+}
diff --git a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts
index 46eae69c..f76db596 100644
--- a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts
+++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts
@@ -1,7 +1,7 @@
 import IAttr from '../attr/IAttr';
 import HTMLElement from '../html-element/HTMLElement';
 import IHTMLScriptElement from './IHTMLScriptElement';
-import ScriptUtility from './ScriptUtility';
+import HTMLScriptElementUtility from './HTMLScriptElementUtility';
 import Event from '../../event/Event';
 import ErrorEvent from '../../event/events/ErrorEvent';
 import INode from '../../nodes/node/INode';
@@ -162,7 +162,7 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip
 		const replacedAttribute = super.setAttributeNode(attribute);
 
 		if (attribute.name === 'src' && attribute.value !== null && this.isConnected) {
-			ScriptUtility.loadExternalScript(this);
+			HTMLScriptElementUtility.loadExternalScript(this);
 		}
 
 		return replacedAttribute;
@@ -192,7 +192,7 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip
 			const src = this.getAttribute('src');
 
 			if (src !== null) {
-				ScriptUtility.loadExternalScript(this);
+				HTMLScriptElementUtility.loadExternalScript(this);
 			} else if (!this.ownerDocument.defaultView.happyDOM.settings.disableJavaScriptEvaluation) {
 				const textContent = this.textContent;
 				const type = this.getAttribute('type');
diff --git a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementUtility.ts b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementUtility.ts
new file mode 100644
index 00000000..1f1afa8c
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementUtility.ts
@@ -0,0 +1,94 @@
+import Document from '../document/Document';
+import Event from '../../event/Event';
+import ErrorEvent from '../../event/events/ErrorEvent';
+import DOMException from '../../exception/DOMException';
+import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum';
+import ResourceFetch from '../../fetch/ResourceFetch';
+import HTMLScriptElement from './HTMLScriptElement';
+
+/**
+ * Helper class for getting the URL relative to a Location object.
+ */
+export default class HTMLScriptElementUtility {
+	/**
+	 * Returns a URL relative to the given Location object.
+	 *
+	 * @param options Options.
+	 * @param options.element Element.
+	 * @param element
+	 */
+	public static async loadExternalScript(element: HTMLScriptElement): Promise<void> {
+		const src = element.getAttribute('src');
+		const async = element.getAttribute('async') !== null;
+
+		if (
+			element.ownerDocument.defaultView.happyDOM.settings.disableJavaScriptFileLoading ||
+			element.ownerDocument.defaultView.happyDOM.settings.disableJavaScriptEvaluation
+		) {
+			this.onError(
+				element,
+				new DOMException(
+					`Failed to load external script "${src}". JavaScript file loading is disabled.`,
+					DOMExceptionNameEnum.notSupportedError
+				)
+			);
+			return;
+		}
+
+		if (async) {
+			(<Document>element.ownerDocument)._readyStateManager.startTask();
+
+			let code = null;
+
+			try {
+				code = await ResourceFetch.fetch(element.ownerDocument, src);
+			} catch (error) {
+				this.onError(element, error);
+				return;
+			}
+
+			element.ownerDocument.defaultView.eval(code);
+			element.dispatchEvent(new Event('load'));
+			(<Document>element.ownerDocument)._readyStateManager.endTask();
+		} else {
+			let code = null;
+
+			try {
+				code = ResourceFetch.fetchSync(element.ownerDocument, src);
+			} catch (error) {
+				this.onError(element, error);
+				return;
+			}
+
+			element.ownerDocument.defaultView.eval(code);
+			element.dispatchEvent(new Event('load'));
+		}
+	}
+
+	/**
+	 * Triggered when an error occurs.
+	 *
+	 * @param element Element.
+	 * @param error Error.
+	 */
+	private static onError(element: HTMLScriptElement, error: Error): void {
+		element.dispatchEvent(
+			new ErrorEvent('error', {
+				message: error.message,
+				error
+			})
+		);
+		element.ownerDocument.defaultView.dispatchEvent(
+			new ErrorEvent('error', {
+				message: error.message,
+				error
+			})
+		);
+		if (
+			!element['_listeners']['error'] &&
+			!element.ownerDocument.defaultView['_listeners']['error']
+		) {
+			element.ownerDocument.defaultView.console.error(error);
+		}
+	}
+}
diff --git a/packages/happy-dom/src/nodes/html-script-element/ScriptUtility.ts b/packages/happy-dom/src/nodes/html-script-element/ScriptUtility.ts
deleted file mode 100644
index aef329a4..00000000
--- a/packages/happy-dom/src/nodes/html-script-element/ScriptUtility.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import { Document } from '../..';
-import Event from '../../event/Event';
-import ErrorEvent from '../../event/events/ErrorEvent';
-import ResourceFetch from '../../fetch/ResourceFetch';
-import HTMLScriptElement from './HTMLScriptElement';
-
-/**
- * Helper class for getting the URL relative to a Location object.
- */
-export default class ScriptUtility {
-	/**
-	 * Returns a URL relative to the given Location object.
-	 *
-	 * @param options Options.
-	 * @param options.element Element.
-	 * @param element
-	 */
-	public static async loadExternalScript(element: HTMLScriptElement): Promise<void> {
-		const src = element.getAttribute('src');
-		const async = element.getAttribute('async') !== null;
-
-		if (
-			element.ownerDocument.defaultView.happyDOM.settings.disableJavaScriptFileLoading ||
-			element.ownerDocument.defaultView.happyDOM.settings.disableJavaScriptEvaluation
-		) {
-			return;
-		}
-
-		if (async) {
-			let code = null;
-			(<Document>element.ownerDocument)._readyStateManager.startTask();
-			try {
-				code = await ResourceFetch.fetch(element.ownerDocument, src);
-			} catch (error) {
-				element.dispatchEvent(
-					new ErrorEvent('error', {
-						message: error.message,
-						error
-					})
-				);
-				element.ownerDocument.defaultView.dispatchEvent(
-					new ErrorEvent('error', {
-						message: error.message,
-						error
-					})
-				);
-				if (
-					!element['_listeners']['error'] &&
-					!element.ownerDocument.defaultView['_listeners']['error']
-				) {
-					element.ownerDocument.defaultView.console.error(error);
-				}
-			}
-			if (code) {
-				element.ownerDocument.defaultView.eval(code);
-				element.dispatchEvent(new Event('load'));
-			}
-			(<Document>element.ownerDocument)._readyStateManager.endTask();
-		} else {
-			let code = null;
-			try {
-				code = ResourceFetch.fetchSync(element.ownerDocument, src);
-			} catch (error) {
-				element.dispatchEvent(
-					new ErrorEvent('error', {
-						message: error.message,
-						error
-					})
-				);
-				element.ownerDocument.defaultView.dispatchEvent(
-					new ErrorEvent('error', {
-						message: error.message,
-						error
-					})
-				);
-				if (
-					!element['_listeners']['error'] &&
-					!element.ownerDocument.defaultView['_listeners']['error']
-				) {
-					element.ownerDocument.defaultView.console.error(error);
-				}
-			}
-			if (code) {
-				element.ownerDocument.defaultView.eval(code);
-				element.dispatchEvent(new Event('load'));
-			}
-		}
-	}
-}


Test patch:
diff --git a/packages/happy-dom/test/nodes/html-link-element/HTMLLinkElement.test.ts b/packages/happy-dom/test/nodes/html-link-element/HTMLLinkElement.test.ts
index bfff27e7..ca5b2ddb 100644
--- a/packages/happy-dom/test/nodes/html-link-element/HTMLLinkElement.test.ts
+++ b/packages/happy-dom/test/nodes/html-link-element/HTMLLinkElement.test.ts
@@ -3,6 +3,7 @@ import IWindow from '../../../src/window/IWindow';
 import IDocument from '../../../src/nodes/document/IDocument';
 import IHTMLLinkElement from '../../../src/nodes/html-link-element/IHTMLLinkElement';
 import ResourceFetch from '../../../src/fetch/ResourceFetch';
+import ErrorEvent from '../../../src/event/events/ErrorEvent';
 
 describe('HTMLLinkElement', () => {
 	let window: IWindow;
@@ -74,7 +75,7 @@ describe('HTMLLinkElement', () => {
 			expect(element.getAttribute('href')).toBe('test');
 		});
 
-		it('Loads and evaluates an external CSS file when the attribute "href" and "rel" is set and the element is connected to DOM.', (done) => {
+		it('Loads and evaluates an external CSS file when the attribute "href" and "rel" is set and the element is connected to DOM.', async () => {
 			const element = <IHTMLLinkElement>document.createElement('link');
 			const css = 'div { background: red; }';
 			let loadedDocument = null;
@@ -98,17 +99,16 @@ describe('HTMLLinkElement', () => {
 			element.rel = 'stylesheet';
 			element.href = 'test';
 
-			setTimeout(() => {
-				expect(loadedDocument).toBe(document);
-				expect(loadedURL).toBe('test');
-				expect(element.sheet.cssRules.length).toBe(1);
-				expect(element.sheet.cssRules[0].cssText).toBe('div { background: red; }');
-				expect(loadEvent.target).toBe(element);
-				done();
-			}, 0);
+			await window.happyDOM.whenAsyncComplete();
+
+			expect(loadedDocument).toBe(document);
+			expect(loadedURL).toBe('test');
+			expect(element.sheet.cssRules.length).toBe(1);
+			expect(element.sheet.cssRules[0].cssText).toBe('div { background: red; }');
+			expect(loadEvent.target).toBe(element);
 		});
 
-		it('Triggers error event when fetching a CSS file fails during setting the "href" and "rel" attributes.', (done) => {
+		it('Triggers error event when fetching a CSS file fails during setting the "href" and "rel" attributes.', async () => {
 			const element = <IHTMLLinkElement>document.createElement('link');
 			const thrownError = new Error('error');
 			let errorEvent = null;
@@ -126,11 +126,10 @@ describe('HTMLLinkElement', () => {
 			element.rel = 'stylesheet';
 			element.href = 'test';
 
-			setTimeout(() => {
-				expect(errorEvent.error).toEqual(thrownError);
-				expect(errorEvent.message).toEqual('error');
-				done();
-			}, 0);
+			await window.happyDOM.whenAsyncComplete();
+
+			expect(errorEvent.error).toEqual(thrownError);
+			expect(errorEvent.message).toEqual('error');
 		});
 
 		it('Does not load and evaluate external CSS files if the element is not connected to DOM.', () => {
@@ -156,7 +155,7 @@ describe('HTMLLinkElement', () => {
 	});
 
 	describe('set isConnected()', () => {
-		it('Loads and evaluates an external script when "href" attribute has been set, but does not evaluate text content.', (done) => {
+		it('Loads and evaluates an external CSS file when "href" attribute has been set, but does not evaluate text content.', async () => {
 			const element = <IHTMLLinkElement>document.createElement('link');
 			const css = 'div { background: red; }';
 			let loadEvent = null;
@@ -179,17 +178,16 @@ describe('HTMLLinkElement', () => {
 
 			document.body.appendChild(element);
 
-			setTimeout(() => {
-				expect(loadedDocument).toBe(document);
-				expect(loadedURL).toBe('test');
-				expect(element.sheet.cssRules.length).toBe(1);
-				expect(element.sheet.cssRules[0].cssText).toBe('div { background: red; }');
-				expect(loadEvent.target).toBe(element);
-				done();
-			}, 0);
+			await window.happyDOM.whenAsyncComplete();
+
+			expect(loadedDocument).toBe(document);
+			expect(loadedURL).toBe('test');
+			expect(element.sheet.cssRules.length).toBe(1);
+			expect(element.sheet.cssRules[0].cssText).toBe('div { background: red; }');
+			expect(loadEvent.target).toBe(element);
 		});
 
-		it('Triggers error event when fetching a CSS file fails while appending the element to the document.', (done) => {
+		it('Triggers error event when fetching a CSS file fails while appending the element to the document.', async () => {
 			const element = <IHTMLLinkElement>document.createElement('link');
 			const thrownError = new Error('error');
 			let errorEvent = null;
@@ -206,14 +204,13 @@ describe('HTMLLinkElement', () => {
 
 			document.body.appendChild(element);
 
-			setTimeout(() => {
-				expect(errorEvent.error).toEqual(thrownError);
-				expect(errorEvent.message).toEqual('error');
-				done();
-			}, 0);
+			await window.happyDOM.whenAsyncComplete();
+
+			expect(errorEvent.error).toEqual(thrownError);
+			expect(errorEvent.message).toEqual('error');
 		});
 
-		it('Does not load external scripts when "href" attribute has been set if the element is not connected to DOM.', () => {
+		it('Does not load external CSS file when "href" attribute has been set if the element is not connected to DOM.', () => {
 			const element = <IHTMLLinkElement>document.createElement('link');
 			const css = 'div { background: red; }';
 			let loadedDocument = null;
@@ -234,5 +231,23 @@ describe('HTMLLinkElement', () => {
 			expect(loadedURL).toBe(null);
 			expect(element.sheet).toBe(null);
 		});
+
+		it('Triggers an error event when "window.happyDOM.settings.disableCSSFileLoading" is set to "true".', async () => {
+			const element = <IHTMLLinkElement>document.createElement('link');
+			let errorEvent: ErrorEvent = null;
+
+			element.rel = 'stylesheet';
+			element.href = '/test/path/file.css';
+			element.addEventListener('error', (event: ErrorEvent) => (errorEvent = event));
+
+			window.happyDOM.settings.disableCSSFileLoading = true;
+
+			document.body.appendChild(element);
+
+			expect(element.sheet).toBe(null);
+			expect(errorEvent.message).toBe(
+				'Failed to load external stylesheet "/test/path/file.css". CSS file loading is disabled.'
+			);
+		});
 	});
 });
diff --git a/packages/happy-dom/test/nodes/html-script-element/HTMLScriptElement.test.ts b/packages/happy-dom/test/nodes/html-script-element/HTMLScriptElement.test.ts
index 68d746b8..c0ffd68c 100644
--- a/packages/happy-dom/test/nodes/html-script-element/HTMLScriptElement.test.ts
+++ b/packages/happy-dom/test/nodes/html-script-element/HTMLScriptElement.test.ts
@@ -1,7 +1,9 @@
 import Window from '../../../src/window/Window';
 import Document from '../../../src/nodes/document/Document';
 import HTMLScriptElement from '../../../src/nodes/html-script-element/HTMLScriptElement';
-import ScriptUtility from '../../../src/nodes/html-script-element/ScriptUtility';
+import IDocument from '../../../src/nodes/document/IDocument';
+import IResponse from '../../../src/fetch/types/IResponse';
+import ResourceFetch from '../../../src/fetch/ResourceFetch';
 
 describe('HTMLScriptElement', () => {
 	let window: Window;
@@ -74,12 +76,15 @@ describe('HTMLScriptElement', () => {
 			expect(element.getAttribute('src')).toBe('test');
 		});
 
-		it('Loads and evaluates an external script when the attribute "src" is set and the element is connected to DOM.', () => {
+		it('Loads and evaluates an external script when the attribute "src" is set and the element is connected to DOM.', async () => {
 			const element = <HTMLScriptElement>document.createElement('script');
-			let loadedElement = null;
 
-			jest.spyOn(ScriptUtility, 'loadExternalScript').mockImplementation(async (element) => {
-				loadedElement = element;
+			jest.spyOn(window, 'fetch').mockImplementation(() => {
+				return Promise.resolve(<IResponse>{
+					text: async () => 'globalThis.test = "test";',
+					ok: true,
+					status: 200
+				});
 			});
 
 			document.body.appendChild(element);
@@ -87,21 +92,28 @@ describe('HTMLScriptElement', () => {
 			element.async = true;
 			element.src = 'test';
 
-			expect(loadedElement).toBe(element);
+			await window.happyDOM.whenAsyncComplete();
+
+			expect(window['test']).toBe('test');
 		});
 
-		it('Does not evaluate script if the element is not connected to DOM.', () => {
+		it('Does not evaluate script if the element is not connected to DOM.', async () => {
 			const element = <HTMLScriptElement>document.createElement('script');
-			let loadedElement = null;
 
-			jest.spyOn(ScriptUtility, 'loadExternalScript').mockImplementation(async (element) => {
-				loadedElement = element;
+			jest.spyOn(window, 'fetch').mockImplementation(() => {
+				return Promise.resolve(<IResponse>{
+					text: async () => 'globalThis.test = "test";',
+					ok: true,
+					status: 200
+				});
 			});
 
 			element.async = true;
 			element.src = 'test';
 
-			expect(loadedElement).toBe(null);
+			await window.happyDOM.whenAsyncComplete();
+
+			expect(window['test']).toBe(undefined);
 		});
 	});
 
@@ -146,6 +158,112 @@ describe('HTMLScriptElement', () => {
 			expect(window['test']).toBe('test');
 		});
 
+		it('Loads external script asynchronously.', async () => {
+			let fetchedURL = null;
+			let loadEvent = null;
+
+			jest.spyOn(window, 'fetch').mockImplementation((url: string) => {
+				fetchedURL = url;
+				return Promise.resolve(<IResponse>{
+					text: async () => 'globalThis.test = "test";',
+					ok: true
+				});
+			});
+
+			const script = <HTMLScriptElement>window.document.createElement('script');
+			script.src = 'path/to/script/';
+			script.async = true;
+			script.addEventListener('load', (event) => {
+				loadEvent = event;
+			});
+
+			document.body.appendChild(script);
+
+			await window.happyDOM.whenAsyncComplete();
+
+			expect(loadEvent.target).toBe(script);
+			expect(fetchedURL).toBe('path/to/script/');
+			expect(window['test']).toBe('test');
+		});
+
+		it('Triggers error event when loading external script asynchronously.', async () => {
+			let errorEvent = null;
+
+			jest.spyOn(window, 'fetch').mockImplementation(() => {
+				return Promise.resolve(<IResponse>{
+					text: () => null,
+					ok: false,
+					status: 404
+				});
+			});
+
+			const script = <HTMLScriptElement>window.document.createElement('script');
+			script.src = 'path/to/script/';
+			script.async = true;
+			script.addEventListener('error', (event) => {
+				errorEvent = event;
+			});
+
+			document.body.appendChild(script);
+
+			await window.happyDOM.whenAsyncComplete();
+
+			expect(errorEvent.message).toBe(
+				'Failed to perform request to "path/to/script/". Status code: 404'
+			);
+		});
+
+		it('Loads external script synchronously with relative URL.', async () => {
+			let fetchedDocument = null;
+			let fetchedURL = null;
+			let loadEvent = null;
+
+			window.location.href = 'https://localhost:8080/base/';
+
+			jest
+				.spyOn(ResourceFetch, 'fetchSync')
+				.mockImplementation((document: IDocument, url: string) => {
+					fetchedDocument = document;
+					fetchedURL = url;
+					return 'globalThis.test = "test";';
+				});
+
+			const script = <HTMLScriptElement>window.document.createElement('script');
+			script.src = 'path/to/script/';
+			script.addEventListener('load', (event) => {
+				loadEvent = event;
+			});
+
+			document.body.appendChild(script);
+
+			expect(loadEvent.target).toBe(script);
+			expect(fetchedDocument).toBe(document);
+			expect(fetchedURL).toBe('path/to/script/');
+			expect(window['test']).toBe('test');
+		});
+
+		it('Triggers error event when loading external script synchronously with relative URL.', () => {
+			const thrownError = new Error('error');
+			let errorEvent = null;
+
+			window.location.href = 'https://localhost:8080/base/';
+
+			jest.spyOn(ResourceFetch, 'fetchSync').mockImplementation(() => {
+				throw thrownError;
+			});
+
+			const script = <HTMLScriptElement>window.document.createElement('script');
+			script.src = 'path/to/script/';
+			script.addEventListener('error', (event) => {
+				errorEvent = event;
+			});
+
+			document.body.appendChild(script);
+
+			expect(errorEvent.message).toBe('error');
+			expect(errorEvent.error).toBe(thrownError);
+		});
+
 		it('Does not evaluate types that are not supported.', () => {
 			const div = document.createElement('div');
 			const element = <HTMLScriptElement>document.createElement('script');
@@ -190,34 +308,106 @@ describe('HTMLScriptElement', () => {
 
 		it('Loads and evaluates an external script when "src" attribute has been set, but does not evaluate text content.', () => {
 			const element = <HTMLScriptElement>document.createElement('script');
-			let loadedElement = null;
 
-			jest.spyOn(ScriptUtility, 'loadExternalScript').mockImplementation(async (element) => {
-				loadedElement = element;
-			});
+			jest
+				.spyOn(ResourceFetch, 'fetchSync')
+				.mockImplementation(() => 'globalThis.testFetch = "test";');
 
 			element.src = 'test';
-			element.text = 'globalThis.test = "test";';
+			element.text = 'globalThis.testContent = "test";';
 
 			document.body.appendChild(element);
 
-			expect(window['test']).toBe(undefined);
-			expect(loadedElement).toBe(element);
+			expect(window['testFetch']).toBe('test');
+			expect(window['testContent']).toBe(undefined);
 		});
 
 		it('Does not load external scripts when "src" attribute has been set if the element is not connected to DOM.', () => {
 			const element = <HTMLScriptElement>document.createElement('script');
-			let loadedElement = null;
 
-			jest.spyOn(ScriptUtility, 'loadExternalScript').mockImplementation(async (element) => {
-				loadedElement = element;
-			});
+			jest
+				.spyOn(ResourceFetch, 'fetchSync')
+				.mockImplementation(() => 'globalThis.testFetch = "test";');
 
 			element.src = 'test';
 			element.text = 'globalThis.test = "test";';
 
-			expect(window['test']).toBe(undefined);
-			expect(loadedElement).toBe(null);
+			expect(window['testFetch']).toBe(undefined);
+			expect(window['testContent']).toBe(undefined);
+		});
+
+		it('Triggers an error event when attempting to perform an asynchrounous request and "window.happyDOM.settings.disableJavaScriptFileLoading" is set to "true".', () => {
+			let errorEvent = null;
+
+			const script = <HTMLScriptElement>window.document.createElement('script');
+			script.src = 'path/to/script/';
+			script.async = true;
+			script.addEventListener('error', (event) => {
+				errorEvent = event;
+			});
+
+			window.happyDOM.settings.disableJavaScriptFileLoading = true;
+
+			document.body.appendChild(script);
+
+			expect(errorEvent.message).toBe(
+				'Failed to load external script "path/to/script/". JavaScript file loading is disabled.'
+			);
+		});
+
+		it('Triggers an error event when attempting to perform a synchrounous request and "window.happyDOM.settings.disableJavaScriptFileLoading" is set to "true".', () => {
+			let errorEvent = null;
+
+			const script = <HTMLScriptElement>window.document.createElement('script');
+			script.src = 'path/to/script/';
+			script.addEventListener('error', (event) => {
+				errorEvent = event;
+			});
+
+			window.happyDOM.settings.disableJavaScriptFileLoading = true;
+
+			document.body.appendChild(script);
+
+			expect(errorEvent.message).toBe(
+				'Failed to load external script "path/to/script/". JavaScript file loading is disabled.'
+			);
+		});
+
+		it('Triggers an error event when attempting to perform an asynchrounous request and "window.happyDOM.settings.disableJavaScriptEvaluation" is set to "true".', () => {
+			let errorEvent = null;
+
+			const script = <HTMLScriptElement>window.document.createElement('script');
+			script.src = 'path/to/script/';
+			script.async = true;
+			script.addEventListener('error', (event) => {
+				errorEvent = event;
+			});
+
+			window.happyDOM.settings.disableJavaScriptEvaluation = true;
+
+			document.body.appendChild(script);
+
+			expect(errorEvent.message).toBe(
+				'Failed to load external script "path/to/script/". JavaScript file loading is disabled.'
+			);
+		});
+
+		it('Triggers an error event when attempting to perform a synchrounous request and "window.happyDOM.settings.disableJavaScriptEvaluation" is set to "true".', () => {
+			let errorEvent = null;
+
+			const script = <HTMLScriptElement>window.document.createElement('script');
+			script.src = 'path/to/script/';
+			script.addEventListener('error', (event) => {
+				errorEvent = event;
+			});
+
+			window.happyDOM.settings.disableJavaScriptEvaluation = true;
+
+			document.body.appendChild(script);
+
+			expect(errorEvent.message).toBe(
+				'Failed to load external script "path/to/script/". JavaScript file loading is disabled.'
+			);
 		});
 	});
 });
diff --git a/packages/happy-dom/test/nodes/html-script-element/ScriptUtility.test.ts b/packages/happy-dom/test/nodes/html-script-element/ScriptUtility.test.ts
deleted file mode 100644
index 5377acc6..00000000
--- a/packages/happy-dom/test/nodes/html-script-element/ScriptUtility.test.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import Window from '../../../src/window/Window';
-import IWindow from '../../../src/window/IWindow';
-import IDocument from '../../../src/nodes/document/IDocument';
-import ScriptUtility from '../../../src/nodes/html-script-element/ScriptUtility';
-import IResponse from '../../../src/fetch/types/IResponse';
-import HTMLScriptElement from '../../../src/nodes/html-script-element/HTMLScriptElement';
-import ResourceFetch from '../../../src/fetch/ResourceFetch';
-
-describe('ScriptUtility', () => {
-	let window: IWindow;
-	let document: IDocument;
-
-	beforeEach(() => {
-		window = new Window();
-		document = window.document;
-	});
-
-	afterEach(() => {
-		jest.restoreAllMocks();
-	});
-
-	describe('loadExternalScript()', () => {
-		it('Loads external script asynchronously.', async () => {
-			let fetchedURL = null;
-			let loadEvent = null;
-
-			jest.spyOn(window, 'fetch').mockImplementation((url: string) => {
-				fetchedURL = url;
-				return Promise.resolve(<IResponse>{
-					text: () => Promise.resolve('globalThis.test = "test";'),
-					ok: true
-				});
-			});
-
-			const script = <HTMLScriptElement>window.document.createElement('script');
-			script.src = 'path/to/script/';
-			script.async = true;
-			script.addEventListener('load', (event) => {
-				loadEvent = event;
-			});
-
-			await ScriptUtility.loadExternalScript(script);
-
-			expect(loadEvent.target).toBe(script);
-			expect(fetchedURL).toBe('path/to/script/');
-			expect(window['test']).toBe('test');
-		});
-
-		it('Triggers error event when loading external script asynchronously.', async () => {
-			let errorEvent = null;
-
-			jest.spyOn(window, 'fetch').mockImplementation(() => {
-				return Promise.resolve(<IResponse>{
-					text: () => null,
-					ok: false,
-					status: 404
-				});
-			});
-
-			const script = <HTMLScriptElement>window.document.createElement('script');
-			script.src = 'path/to/script/';
-			script.async = true;
-			script.addEventListener('error', (event) => {
-				errorEvent = event;
-			});
-
-			await ScriptUtility.loadExternalScript(script);
-
-			expect(errorEvent.message).toBe(
-				'Failed to perform request to "path/to/script/". Status code: 404'
-			);
-		});
-
-		it('Loads external script synchronously with relative URL.', async () => {
-			let fetchedDocument = null;
-			let fetchedURL = null;
-			let loadEvent = null;
-
-			window.location.href = 'https://localhost:8080/base/';
-
-			jest
-				.spyOn(ResourceFetch, 'fetchSync')
-				.mockImplementation((document: IDocument, url: string) => {
-					fetchedDocument = document;
-					fetchedURL = url;
-					return 'globalThis.test = "test";';
-				});
-
-			const script = <HTMLScriptElement>window.document.createElement('script');
-			script.src = 'path/to/script/';
-			script.addEventListener('load', (event) => {
-				loadEvent = event;
-			});
-
-			await ScriptUtility.loadExternalScript(script);
-
-			expect(loadEvent.target).toBe(script);
-			expect(fetchedDocument).toBe(document);
-			expect(fetchedURL).toBe('path/to/script/');
-			expect(window['test']).toBe('test');
-		});
-
-		it('Triggers error event when loading external script synchronously with relative URL.', () => {
-			const thrownError = new Error('error');
-			let errorEvent = null;
-
-			window.location.href = 'https://localhost:8080/base/';
-
-			jest.spyOn(ResourceFetch, 'fetchSync').mockImplementation(() => {
-				throw thrownError;
-			});
-
-			const script = <HTMLScriptElement>window.document.createElement('script');
-			script.src = 'path/to/script/';
-			script.addEventListener('error', (event) => {
-				errorEvent = event;
-			});
-
-			ScriptUtility.loadExternalScript(script);
-
-			expect(errorEvent.message).toBe('error');
-			expect(errorEvent.error).toBe(thrownError);
-		});
-	});
-});