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 96d3d0526..14e02a390 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
- ) {
- (this.ownerDocument)._readyStateManager.startTask();
- ResourceFetch.fetch(this.ownerDocument, href)
- .then((code) => {
- const styleSheet = new CSSStyleSheet();
- styleSheet.replaceSync(code);
- (this.sheet) = styleSheet;
- this.dispatchEvent(new Event('load'));
- (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
- })
- );
- (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') {
- (this.ownerDocument)._readyStateManager.startTask();
- ResourceFetch.fetch(this.ownerDocument, href)
- .then((code) => {
- const styleSheet = new CSSStyleSheet();
- styleSheet.replaceSync(code);
- (this.sheet) = styleSheet;
- this.dispatchEvent(new Event('load'));
- (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
- })
- );
- (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 000000000..a8dc437a9
--- /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 {
+ 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;
+ }
+
+ (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);
+ (element.sheet) = styleSheet;
+ element.dispatchEvent(new Event('load'));
+ (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
+ })
+ );
+ (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 46eae69ca..f76db5961 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 000000000..1f1afa8cd
--- /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 {
+ 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) {
+ (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'));
+ (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 aef329a4c..000000000
--- 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 {
- 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;
- (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'));
- }
- (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'));
- }
- }
- }
-}
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 bfff27e70..ca5b2ddb8 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 = 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 = 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 = 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 = 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 = 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 = 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 68d746b84..c0ffd68c8 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 = document.createElement('script');
- let loadedElement = null;
- jest.spyOn(ScriptUtility, 'loadExternalScript').mockImplementation(async (element) => {
- loadedElement = element;
+ jest.spyOn(window, 'fetch').mockImplementation(() => {
+ return Promise.resolve({
+ 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 = document.createElement('script');
- let loadedElement = null;
- jest.spyOn(ScriptUtility, 'loadExternalScript').mockImplementation(async (element) => {
- loadedElement = element;
+ jest.spyOn(window, 'fetch').mockImplementation(() => {
+ return Promise.resolve({
+ 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({
+ text: async () => 'globalThis.test = "test";',
+ ok: true
+ });
+ });
+
+ const script = 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({
+ text: () => null,
+ ok: false,
+ status: 404
+ });
+ });
+
+ const script = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 5377acc6d..000000000
--- 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({
- text: () => Promise.resolve('globalThis.test = "test";'),
- ok: true
- });
- });
-
- const script = 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({
- text: () => null,
- ok: false,
- status: 404
- });
- });
-
- const script = 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 = 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 = 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);
- });
- });
-});