From 80f85911b0b693e3e09b926d5c4fc33f707d7084 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Thu, 14 Mar 2024 00:41:07 +0100 Subject: [PATCH] fix: [#1135] Adds support for returning URL relative to window location in HTMLLinkElement.href, HTMLImageElement.src and HTMLScriptElement.src --- .../src/browser/utilities/BrowserFrameURL.ts | 14 +- .../html-anchor-element/HTMLAnchorElement.ts | 204 ++++++++++++------ .../HTMLAnchorElementNamedNodeMap.ts | 20 +- .../HTMLAnchorElementUtility.ts | 48 ----- .../html-button-element/HTMLButtonElement.ts | 2 +- .../html-form-element/HTMLFormElement.ts | 6 +- .../html-image-element/HTMLImageElement.ts | 13 +- .../html-input-element/HTMLInputElement.ts | 2 +- .../html-link-element/HTMLLinkElement.ts | 11 +- .../HTMLLinkElementStyleSheetLoader.ts | 2 - .../html-script-element/HTMLScriptElement.ts | 13 +- .../HTMLScriptElementScriptLoader.ts | 2 - .../HTMLAnchorElement.test.ts | 73 ------- .../HTMLImageElement.test.ts | 25 ++- .../html-link-element/HTMLLinkElement.test.ts | 33 +-- .../HTMLScriptElement.test.ts | 37 ++-- 16 files changed, 246 insertions(+), 259 deletions(-) delete mode 100644 packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementUtility.ts diff --git a/packages/happy-dom/src/browser/utilities/BrowserFrameURL.ts b/packages/happy-dom/src/browser/utilities/BrowserFrameURL.ts index 4ca40163f..c28df70dd 100644 --- a/packages/happy-dom/src/browser/utilities/BrowserFrameURL.ts +++ b/packages/happy-dom/src/browser/utilities/BrowserFrameURL.ts @@ -1,6 +1,4 @@ import IBrowserFrame from '../types/IBrowserFrame.js'; -import DOMException from '../../exception/DOMException.js'; -import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; import { URL } from 'url'; /** @@ -24,17 +22,7 @@ export default class BrowserFrameURL { try { return new URL(url, frame.window.location.href); } catch (e) { - if (frame.window.location.hostname) { - throw new DOMException( - `Failed to construct URL from string "${url}".`, - DOMExceptionNameEnum.uriMismatchError - ); - } else { - throw new DOMException( - `Failed to construct URL from string "${url}" relative to URL "${frame.window.location.href}".`, - DOMExceptionNameEnum.uriMismatchError - ); - } + return new URL('about:blank'); } } } diff --git a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts index f1f157c34..28354ac09 100644 --- a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts +++ b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts @@ -4,7 +4,6 @@ import DOMTokenList from '../../dom-token-list/DOMTokenList.js'; import IDOMTokenList from '../../dom-token-list/IDOMTokenList.js'; import IHTMLAnchorElement from './IHTMLAnchorElement.js'; import URL from '../../url/URL.js'; -import HTMLAnchorElementUtility from './HTMLAnchorElementUtility.js'; import INamedNodeMap from '../../named-node-map/INamedNodeMap.js'; import HTMLAnchorElementNamedNodeMap from './HTMLAnchorElementNamedNodeMap.js'; import Event from '../../event/Event.js'; @@ -22,7 +21,6 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho this ); public [PropertySymbol.relList]: DOMTokenList = null; - public [PropertySymbol.url]: URL | null = null; /** * Returns download. @@ -48,7 +46,17 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @returns Hash. */ public get hash(): string { - return this[PropertySymbol.url]?.hash ?? ''; + const href = this.getAttribute('href'); + if (href.startsWith('#')) { + return href; + } + let url: URL; + try { + url = new URL(this.href); + } catch (e) { + return ''; + } + return url.hash; } /** @@ -57,10 +65,14 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @param hash Hash. */ public set hash(hash: string) { - if (this[PropertySymbol.url] && !HTMLAnchorElementUtility.isBlobURL(this[PropertySymbol.url])) { - this[PropertySymbol.url].hash = hash; - this.setAttribute('href', this[PropertySymbol.url].toString()); + let url: URL; + try { + url = new URL(this.href); + } catch (e) { + return; } + url.hash = hash; + this.href = url.href; } /** @@ -68,12 +80,17 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * * @returns Href. */ - public get href(): string | null { - if (this[PropertySymbol.url]) { - return this[PropertySymbol.url].toString(); + public get href(): string { + if (!this.hasAttribute('href')) { + return ''; } - return this.getAttribute('href') || ''; + try { + return new URL(this.getAttribute('href'), this[PropertySymbol.ownerDocument].location.href) + .href; + } catch (e) { + return this.getAttribute('href'); + } } /** @@ -109,7 +126,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @returns Origin. */ public get origin(): string { - return this[PropertySymbol.url]?.origin ?? ''; + try { + return new URL(this.href).origin; + } catch (e) { + return ''; + } } /** @@ -136,7 +157,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @returns Protocol. */ public get protocol(): string { - return this[PropertySymbol.url]?.protocol ?? ''; + try { + return new URL(this.href).protocol; + } catch (e) { + return ''; + } } /** @@ -145,10 +170,14 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @param protocol Protocol. */ public set protocol(protocol: string) { - if (this[PropertySymbol.url] && !HTMLAnchorElementUtility.isBlobURL(this[PropertySymbol.url])) { - this[PropertySymbol.url].protocol = protocol; - this.setAttribute('href', this[PropertySymbol.url].toString()); + let url: URL; + try { + url = new URL(this.href); + } catch (e) { + return; } + url.protocol = protocol; + this.href = url.href; } /** @@ -157,7 +186,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @returns Username. */ public get username(): string { - return this[PropertySymbol.url]?.username ?? ''; + try { + return new URL(this.href).username; + } catch (e) { + return ''; + } } /** @@ -166,15 +199,14 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @param username Username. */ public set username(username: string) { - if ( - this[PropertySymbol.url] && - !HTMLAnchorElementUtility.isBlobURL(this[PropertySymbol.url]) && - this[PropertySymbol.url].host && - this[PropertySymbol.url].protocol != 'file' - ) { - this[PropertySymbol.url].username = username; - this.setAttribute('href', this[PropertySymbol.url].toString()); + let url: URL; + try { + url = new URL(this.href); + } catch (e) { + return; } + url.username = username; + this.href = url.href; } /** @@ -183,7 +215,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @returns Password. */ public get password(): string { - return this[PropertySymbol.url]?.password ?? ''; + try { + return new URL(this.href).password; + } catch (e) { + return ''; + } } /** @@ -192,15 +228,14 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @param password Password. */ public set password(password: string) { - if ( - this[PropertySymbol.url] && - !HTMLAnchorElementUtility.isBlobURL(this[PropertySymbol.url]) && - this[PropertySymbol.url].host && - this[PropertySymbol.url].protocol != 'file' - ) { - this[PropertySymbol.url].password = password; - this.setAttribute('href', this[PropertySymbol.url].toString()); + let url: URL; + try { + url = new URL(this.href); + } catch (e) { + return; } + url.password = password; + this.href = url.href; } /** @@ -209,7 +244,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @returns Pathname. */ public get pathname(): string { - return this[PropertySymbol.url]?.pathname ?? ''; + try { + return new URL(this.href).pathname; + } catch (e) { + return ''; + } } /** @@ -218,10 +257,14 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @param pathname Pathname. */ public set pathname(pathname: string) { - if (this[PropertySymbol.url] && !HTMLAnchorElementUtility.isBlobURL(this[PropertySymbol.url])) { - this[PropertySymbol.url].pathname = pathname; - this.setAttribute('href', this[PropertySymbol.url].toString()); + let url: URL; + try { + url = new URL(this.href); + } catch (e) { + return; } + url.pathname = pathname; + this.href = url.href; } /** @@ -230,7 +273,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @returns Port. */ public get port(): string { - return this[PropertySymbol.url]?.port ?? ''; + try { + return new URL(this.href).port; + } catch (e) { + return ''; + } } /** @@ -239,15 +286,14 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @param port Port. */ public set port(port: string) { - if ( - this[PropertySymbol.url] && - !HTMLAnchorElementUtility.isBlobURL(this[PropertySymbol.url]) && - this[PropertySymbol.url].host && - this[PropertySymbol.url].protocol != 'file' - ) { - this[PropertySymbol.url].port = port; - this.setAttribute('href', this[PropertySymbol.url].toString()); + let url: URL; + try { + url = new URL(this.href); + } catch (e) { + return; } + url.port = port; + this.href = url.href; } /** @@ -256,7 +302,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @returns Host. */ public get host(): string { - return this[PropertySymbol.url]?.host ?? ''; + try { + return new URL(this.href).host; + } catch (e) { + return ''; + } } /** @@ -265,10 +315,14 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @param host Host. */ public set host(host: string) { - if (this[PropertySymbol.url] && !HTMLAnchorElementUtility.isBlobURL(this[PropertySymbol.url])) { - this[PropertySymbol.url].host = host; - this.setAttribute('href', this[PropertySymbol.url].toString()); + let url: URL; + try { + url = new URL(this.href); + } catch (e) { + return; } + url.host = host; + this.href = url.href; } /** @@ -277,7 +331,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @returns Hostname. */ public get hostname(): string { - return this[PropertySymbol.url]?.hostname ?? ''; + try { + return new URL(this.href).hostname; + } catch (e) { + return ''; + } } /** @@ -286,10 +344,14 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @param hostname Hostname. */ public set hostname(hostname: string) { - if (this[PropertySymbol.url] && !HTMLAnchorElementUtility.isBlobURL(this[PropertySymbol.url])) { - this[PropertySymbol.url].hostname = hostname; - this.setAttribute('href', this[PropertySymbol.url].toString()); + let url: URL; + try { + url = new URL(this.href); + } catch (e) { + return; } + url.hostname = hostname; + this.href = url.href; } /** @@ -346,7 +408,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @returns Search. */ public get search(): string { - return this[PropertySymbol.url]?.search ?? ''; + try { + return new URL(this.href).search; + } catch (e) { + return ''; + } } /** @@ -355,10 +421,14 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho * @param search Search. */ public set search(search: string) { - if (this[PropertySymbol.url] && !HTMLAnchorElementUtility.isBlobURL(this[PropertySymbol.url])) { - this[PropertySymbol.url].search = search; - this.setAttribute('href', this[PropertySymbol.url].toString()); + let url: URL; + try { + url = new URL(this.href); + } catch (e) { + return; } + url.search = search; + this.href = url.href; } /** @@ -433,15 +503,17 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLAncho event instanceof PointerEvent && (event.eventPhase === EventPhaseEnum.atTarget || event.eventPhase === EventPhaseEnum.bubbling) && - !event.defaultPrevented && - this[PropertySymbol.url] + !event.defaultPrevented ) { - this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].open( - this[PropertySymbol.url].toString(), - this.target || '_self' - ); - if (this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].closed) { - event.stopImmediatePropagation(); + const href = this.href; + if (href) { + this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].open( + href, + this.target || '_self' + ); + if (this[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].closed) { + event.stopImmediatePropagation(); + } } } diff --git a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementNamedNodeMap.ts index 8832b3626..1456e44c1 100644 --- a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementNamedNodeMap.ts @@ -2,7 +2,6 @@ import IAttr from '../attr/IAttr.js'; import * as PropertySymbol from '../../PropertySymbol.js'; import HTMLElementNamedNodeMap from '../html-element/HTMLElementNamedNodeMap.js'; import HTMLAnchorElement from './HTMLAnchorElement.js'; -import HTMLAnchorElementUtility from './HTMLAnchorElementUtility.js'; /** * Named Node Map. @@ -23,11 +22,6 @@ export default class HTMLAnchorElementNamedNodeMap extends HTMLElementNamedNodeM this[PropertySymbol.ownerElement][PropertySymbol.relList] ) { this[PropertySymbol.ownerElement][PropertySymbol.relList][PropertySymbol.updateIndices](); - } else if (item[PropertySymbol.name] === 'href') { - this[PropertySymbol.ownerElement][PropertySymbol.url] = HTMLAnchorElementUtility.getUrl( - this[PropertySymbol.ownerElement].ownerDocument, - item[PropertySymbol.value] - ); } return replacedItem || null; @@ -39,15 +33,11 @@ export default class HTMLAnchorElementNamedNodeMap extends HTMLElementNamedNodeM public override [PropertySymbol.removeNamedItem](name: string): IAttr | null { const removedItem = super[PropertySymbol.removeNamedItem](name); - if (removedItem) { - if ( - removedItem[PropertySymbol.name] === 'rel' && - this[PropertySymbol.ownerElement][PropertySymbol.relList] - ) { - this[PropertySymbol.ownerElement][PropertySymbol.relList][PropertySymbol.updateIndices](); - } else if (removedItem[PropertySymbol.name] === 'href') { - this[PropertySymbol.ownerElement][PropertySymbol.url] = null; - } + if ( + removedItem?.[PropertySymbol.name] === 'rel' && + this[PropertySymbol.ownerElement][PropertySymbol.relList] + ) { + this[PropertySymbol.ownerElement][PropertySymbol.relList][PropertySymbol.updateIndices](); } return removedItem; diff --git a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementUtility.ts b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementUtility.ts deleted file mode 100644 index bcdbaa59e..000000000 --- a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElementUtility.ts +++ /dev/null @@ -1,48 +0,0 @@ -import IDocument from '../document/IDocument.js'; -import URL from '../../url/URL.js'; - -/** - * HTML Anchor Element utility. - */ -export default class HTMLAnchorElementUtility { - /** - * Returns "true" if it is a blob URL. - * - * According to spec, if element's url is non-null, its scheme is "blob", and it has an opaque path, then the process of updating properties on the URL should be terminated. - * - * @see https://html.spec.whatwg.org/multipage/links.html#reinitialise-url - * @param url - * @param url URL. - * @returns "true" if blob URL. - */ - public static isBlobURL(url: URL): boolean { - return ( - url && url.protocol === 'blob:' && url.pathname.length > 1 && url.pathname.includes('://') - ); - } - - /** - * Returns URL. - * - * @see https://html.spec.whatwg.org/multipage/links.html#dom-hyperlink-href - * @see https://html.spec.whatwg.org/multipage/links.html#hyperlink - * @param document Document. - * @param href Href. - * @returns URL. - */ - public static getUrl(document: IDocument, href: string | null): URL { - if (!href) { - return null; - } - - const documentUrl = document.location.href; - - try { - return new URL(href.trim(), documentUrl); - } catch (TypeError) { - // Ignore error - } - - return null; - } -} diff --git a/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts b/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts index e5c07dd37..98e089540 100644 --- a/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts +++ b/packages/happy-dom/src/nodes/html-button-element/HTMLButtonElement.ts @@ -136,7 +136,7 @@ export default class HTMLButtonElement extends HTMLElement implements IHTMLButto try { return new URL( - this.getAttribute('formaction') || '', + this.getAttribute('formaction'), this[PropertySymbol.ownerDocument].location.href ).href; } catch (e) { diff --git a/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts b/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts index 4d0f78cb5..c01d68f44 100644 --- a/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts +++ b/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts @@ -129,10 +129,8 @@ export default class HTMLFormElement extends HTMLElement implements IHTMLFormEle } try { - return new URL( - this.getAttribute('action') || '', - this[PropertySymbol.ownerDocument].location.href - ).href; + return new URL(this.getAttribute('action'), this[PropertySymbol.ownerDocument].location.href) + .href; } catch (e) { return ''; } diff --git a/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts b/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts index 22fd304e9..f965c6445 100644 --- a/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts +++ b/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts @@ -244,13 +244,22 @@ export default class HTMLImageElement extends HTMLElement implements IHTMLImageE * @returns Source. */ public get src(): string { - return this.getAttribute('src') || ''; + if (!this.hasAttribute('src')) { + return ''; + } + + try { + return new URL(this.getAttribute('src'), this[PropertySymbol.ownerDocument].location.href) + .href; + } catch (e) { + return this.getAttribute('src'); + } } /** * Sets source. * - * @param source Source. + * @param src Source. */ public set src(src: string) { this.setAttribute('src', src); diff --git a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts index c45170569..437da8715 100644 --- a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts +++ b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts @@ -108,7 +108,7 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE try { return new URL( - this.getAttribute('formaction') || '', + this.getAttribute('formaction'), this[PropertySymbol.ownerDocument].location.href ).href; } catch (e) { 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 069aec408..261110847 100644 --- a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts +++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts @@ -107,7 +107,16 @@ export default class HTMLLinkElement extends HTMLElement implements IHTMLLinkEle * @returns Href. */ public get href(): string { - return this.getAttribute('href') || ''; + if (!this.hasAttribute('href')) { + return ''; + } + + try { + return new URL(this.getAttribute('href'), this[PropertySymbol.ownerDocument].location.href) + .href; + } catch (e) { + return this.getAttribute('href'); + } } /** diff --git a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementStyleSheetLoader.ts b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementStyleSheetLoader.ts index e2f4cd89a..4667d22fe 100644 --- a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementStyleSheetLoader.ts +++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElementStyleSheetLoader.ts @@ -55,8 +55,6 @@ export default class HTMLLinkElementStyleSheetLoader { element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].location.href ).href; } catch (error) { - this.#loadedStyleSheetURL = null; - element.dispatchEvent(new Event('error')); return; } 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 20496e98a..9fb73f9ae 100644 --- a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts +++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts @@ -70,13 +70,22 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip * @returns Source. */ public get src(): string { - return this.getAttribute('src') || ''; + if (!this.hasAttribute('src')) { + return ''; + } + + try { + return new URL(this.getAttribute('src'), this[PropertySymbol.ownerDocument].location.href) + .href; + } catch (e) { + return this.getAttribute('src'); + } } /** * Sets source. * - * @param source Source. + * @param src Source. */ public set src(src: string) { this.setAttribute('src', src); diff --git a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementScriptLoader.ts b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementScriptLoader.ts index 3c2cf6c91..a7d3001ce 100644 --- a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementScriptLoader.ts +++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElementScriptLoader.ts @@ -50,8 +50,6 @@ export default class HTMLScriptElementScriptLoader { element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].location.href ).href; } catch (error) { - this.#loadedScriptURL = null; - element.dispatchEvent(new Event('error')); return; } diff --git a/packages/happy-dom/test/nodes/html-anchor-element/HTMLAnchorElement.test.ts b/packages/happy-dom/test/nodes/html-anchor-element/HTMLAnchorElement.test.ts index 784e190a6..3444b6ecf 100644 --- a/packages/happy-dom/test/nodes/html-anchor-element/HTMLAnchorElement.test.ts +++ b/packages/happy-dom/test/nodes/html-anchor-element/HTMLAnchorElement.test.ts @@ -9,8 +9,6 @@ import IResponse from '../../../src/fetch/types/IResponse.js'; import Fetch from '../../../src/fetch/Fetch.js'; import Browser from '../../../src/browser/Browser.js'; -const BLOB_URL = 'blob:https://mozilla.org'; - describe('HTMLAnchorElement', () => { let window: IWindow; let document: IDocument; @@ -121,14 +119,6 @@ describe('HTMLAnchorElement', () => { element.href = 'test'; expect(element.getAttribute('href')).toBe('test'); }); - - it('Can be set after a blob URL has been defined.', () => { - const element = document.createElement('a'); - element.href = BLOB_URL; - expect(element.href).toBe(BLOB_URL); - element.href = 'https://example.com/'; - expect(element.href).toBe('https://example.com/'); - }); }); describe('get origin()', () => { @@ -170,13 +160,6 @@ describe('HTMLAnchorElement', () => { expect(element.protocol).toBe('http:'); expect(element.href).toBe('http://www.example.com/path?q1=a#xyz'); }); - - it("Can't be modified on blob URLs.", () => { - const element = document.createElement('a'); - element.href = BLOB_URL; - element.protocol = 'http'; - expect(element.protocol).toBe('blob:'); - }); }); describe('get username()', () => { @@ -198,13 +181,6 @@ describe('HTMLAnchorElement', () => { expect(element.username).toBe('user2'); expect(element.href).toBe('https://user2:pw@www.example.com/path?q1=a#xyz'); }); - - it("Can't be modified on blob URLs.", () => { - const element = document.createElement('a'); - element.href = BLOB_URL; - element.username = 'user2'; - expect(element.username).toBe(''); - }); }); describe('get password()', () => { @@ -226,13 +202,6 @@ describe('HTMLAnchorElement', () => { expect(element.password).toBe('pw2'); expect(element.href).toBe('https://user:pw2@www.example.com/path?q1=a#xyz'); }); - - it("Can't be modified on blob URLs.", () => { - const element = document.createElement('a'); - element.href = BLOB_URL; - element.password = 'pw2'; - expect(element.password).toBe(''); - }); }); describe('get host()', () => { @@ -254,13 +223,6 @@ describe('HTMLAnchorElement', () => { expect(element.host).toBe('abc.example2.com'); expect(element.href).toBe('https://abc.example2.com/path?q1=a#xyz'); }); - - it("Can't be modified on blob URLs.", () => { - const element = document.createElement('a'); - element.href = BLOB_URL; - element.host = 'abc.example2.com'; - expect(element.host).toBe(''); - }); }); describe('get hostname()', () => { @@ -282,13 +244,6 @@ describe('HTMLAnchorElement', () => { expect(element.hostname).toBe('abc.example2.com'); expect(element.href).toBe('https://abc.example2.com/path?q1=a#xyz'); }); - - it("Can't be modified on blob URLs.", () => { - const element = document.createElement('a'); - element.href = BLOB_URL; - element.hostname = 'abc.example2.com'; - expect(element.hostname).toBe(''); - }); }); describe('get port()', () => { @@ -313,13 +268,6 @@ describe('HTMLAnchorElement', () => { expect(element.port).toBe('8080'); expect(element.href).toBe('https://www.example.com:8080/path?q1=a#xyz'); }); - - it("Can't be modified on blob URLs.", () => { - const element = document.createElement('a'); - element.href = BLOB_URL; - element.port = '8080'; - expect(element.port).toBe(''); - }); }); describe('get pathname()', () => { @@ -341,13 +289,6 @@ describe('HTMLAnchorElement', () => { expect(element.pathname).toBe('/path2'); expect(element.href).toBe('https://www.example.com/path2?q1=a#xyz'); }); - - it("Can't be modified on blob URLs.", () => { - const element = document.createElement('a'); - element.href = BLOB_URL; - element.pathname = '/path2'; - expect(element.pathname).toBe(BLOB_URL.split(':').slice(1).join(':')); - }); }); describe('get search()', () => { @@ -369,13 +310,6 @@ describe('HTMLAnchorElement', () => { expect(element.search).toBe('?q1=b'); expect(element.href).toBe('https://www.example.com/path?q1=b#xyz'); }); - - it("Can't be modified on blob URLs.", () => { - const element = document.createElement('a'); - element.href = BLOB_URL; - element.search = '?q1=b'; - expect(element.search).toBe(''); - }); }); describe('get hash()', () => { @@ -397,13 +331,6 @@ describe('HTMLAnchorElement', () => { expect(element.hash).toBe('#fgh'); expect(element.href).toBe('https://www.example.com/path?q1=a#fgh'); }); - - it('Can be modified on blob URLs.', () => { - const element = document.createElement('a'); - element.href = BLOB_URL; - element.hash = '#fgh'; - expect(element.hash).toBe(''); - }); }); describe('dispatchEvent()', () => { diff --git a/packages/happy-dom/test/nodes/html-image-element/HTMLImageElement.test.ts b/packages/happy-dom/test/nodes/html-image-element/HTMLImageElement.test.ts index bbdfdb483..db71a2979 100644 --- a/packages/happy-dom/test/nodes/html-image-element/HTMLImageElement.test.ts +++ b/packages/happy-dom/test/nodes/html-image-element/HTMLImageElement.test.ts @@ -19,7 +19,7 @@ describe('HTMLImageElement', () => { }); }); - for (const property of ['alt', 'referrerPolicy', 'sizes', 'src', 'srcset', 'useMap']) { + for (const property of ['alt', 'referrerPolicy', 'sizes', 'srcset', 'useMap']) { describe(`get ${property}()`, () => { it(`Returns the "${property}" attribute.`, () => { const element = document.createElement('img'); @@ -73,6 +73,29 @@ describe('HTMLImageElement', () => { }); } + describe('get src()', () => { + it('Returns the "src" attribute.', () => { + const element = document.createElement('img'); + element.setAttribute('src', 'test'); + expect(element.src).toBe('test'); + }); + + it('Returns URL relative to window location.', () => { + window.happyDOM.setURL('https://localhost:8080/test/path/'); + const element = document.createElement('img'); + element.setAttribute('src', 'test'); + expect(element.src).toBe('https://localhost:8080/test/path/test'); + }); + }); + + describe('set src()', () => { + it('Sets the attribute "src".', () => { + const element = document.createElement('img'); + element.src = 'test'; + expect(element.getAttribute('src')).toBe('test'); + }); + }); + describe('get complete()', () => { it('Returns "false".', () => { const element = document.createElement('img'); 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 fef661d65..f66f9a1c1 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 @@ -23,7 +23,7 @@ describe('HTMLLinkElement', () => { describe('Object.prototype.toString', () => { it('Returns `[object HTMLLinkElement]`', () => { - const element = document.createElement('link'); + const element = document.createElement('link'); expect(Object.prototype.toString.call(element)).toBe('[object HTMLLinkElement]'); }); }); @@ -40,7 +40,7 @@ describe('HTMLLinkElement', () => { ]) { describe(`get ${property}()`, () => { it(`Returns the "${property}" attribute.`, () => { - const element = document.createElement('link'); + const element = document.createElement('link'); element.setAttribute(property, 'test'); expect(element[property]).toBe('test'); }); @@ -48,7 +48,7 @@ describe('HTMLLinkElement', () => { describe(`set ${property}()`, () => { it(`Sets the attribute "${property}".`, () => { - const element = document.createElement('link'); + const element = document.createElement('link'); element[property] = 'test'; expect(element.getAttribute(property)).toBe('test'); }); @@ -57,7 +57,7 @@ describe('HTMLLinkElement', () => { describe('get relList()', () => { it('Returns a DOMTokenList object.', () => { - const element = document.createElement('link'); + const element = document.createElement('link'); element.setAttribute('rel', 'value1 value2'); expect(element.relList.value).toBe('value1 value2'); }); @@ -65,21 +65,28 @@ describe('HTMLLinkElement', () => { describe('get href()', () => { it('Returns the "href" attribute.', () => { - const element = document.createElement('link'); + const element = document.createElement('link'); element.setAttribute('href', 'test'); expect(element.href).toBe('test'); }); + + it('Returns URL relative to window location.', () => { + window.happyDOM.setURL('https://localhost:8080/test/path/'); + const element = document.createElement('link'); + element.setAttribute('href', 'test'); + expect(element.href).toBe('https://localhost:8080/test/path/test'); + }); }); describe('set href()', () => { it('Sets the attribute "href".', () => { - const element = document.createElement('link'); + const element = document.createElement('link'); element.href = 'test'; 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.', async () => { - const element = document.createElement('link'); + const element = document.createElement('link'); const css = 'div { background: red; }'; let loadedWindow: IBrowserWindow | null = null; let loadedURL: string | null = null; @@ -110,7 +117,7 @@ describe('HTMLLinkElement', () => { }); it('Triggers error event when fetching a CSS file fails during setting the "href" and "rel" attributes.', async () => { - const element = document.createElement('link'); + const element = document.createElement('link'); const thrownError = new Error('error'); let errorEvent: ErrorEvent | null = null; @@ -134,7 +141,7 @@ describe('HTMLLinkElement', () => { }); it('Does not load and evaluate external CSS files if the element is not connected to DOM.', () => { - const element = document.createElement('link'); + const element = document.createElement('link'); const css = 'div { background: red; }'; let loadedWindow: IBrowserWindow | null = null; let loadedURL: string | null = null; @@ -155,7 +162,7 @@ describe('HTMLLinkElement', () => { describe('set isConnected()', () => { 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 element = document.createElement('link'); const css = 'div { background: red; }'; let loadEvent: Event | null = null; let loadedWindow: IBrowserWindow | null = null; @@ -185,7 +192,7 @@ describe('HTMLLinkElement', () => { }); it('Triggers error event when fetching a CSS file fails while appending the element to the document.', async () => { - const element = document.createElement('link'); + const element = document.createElement('link'); const thrownError = new Error('error'); let errorEvent: ErrorEvent | null = null; @@ -208,7 +215,7 @@ describe('HTMLLinkElement', () => { }); 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 element = document.createElement('link'); const css = 'div { background: red; }'; let loadedWindow: IBrowserWindow | null = null; let loadedURL: string | null = null; @@ -233,7 +240,7 @@ describe('HTMLLinkElement', () => { }); document = window.document; - const element = document.createElement('link'); + const element = document.createElement('link'); let errorEvent: ErrorEvent | null = null; element.rel = 'stylesheet'; 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 f640619b1..63e84afe8 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 @@ -69,21 +69,28 @@ describe('HTMLScriptElement', () => { describe('get src()', () => { it('Returns the "src" attribute.', () => { - const element = document.createElement('script'); + const element = document.createElement('script'); element.setAttribute('src', 'test'); expect(element.src).toBe('test'); }); + + it('Returns URL relative to window location.', () => { + window.happyDOM.setURL('https://localhost:8080/test/path/'); + const element = document.createElement('script'); + element.setAttribute('src', 'test'); + expect(element.src).toBe('https://localhost:8080/test/path/test'); + }); }); describe('set src()', () => { it('Sets the attribute "src".', () => { - const element = document.createElement('script'); + const element = document.createElement('script'); element.src = 'test'; 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.', async () => { - const element = document.createElement('script'); + const element = document.createElement('script'); vi.spyOn(Fetch.prototype, 'send').mockImplementation( async () => @@ -105,7 +112,7 @@ describe('HTMLScriptElement', () => { }); it('Does not evaluate script if the element is not connected to DOM.', async () => { - const element = document.createElement('script'); + const element = document.createElement('script'); vi.spyOn(Fetch.prototype, 'send').mockImplementation( async () => @@ -127,14 +134,14 @@ describe('HTMLScriptElement', () => { describe('get text()', () => { it('Returns the data of text nodes.', () => { - const element = document.createElement('script'); + const element = document.createElement('script'); const text = document.createTextNode('test'); element.appendChild(text); expect(element.text).toBe('test'); }); it('Replaces all child nodes with a text node.', () => { - const element = document.createElement('script'); + const element = document.createElement('script'); const text = document.createTextNode('test'); element.appendChild(text); element.text = 'test2'; @@ -144,7 +151,7 @@ describe('HTMLScriptElement', () => { describe('set isConnected()', () => { it('Evaluates the text content as code when appended to an element that is connected to the document.', () => { - const element = document.createElement('script'); + const element = document.createElement('script'); element.text = 'globalThis.test = "test";globalThis.currentScript = document.currentScript;'; document.body.appendChild(element); expect(window['test']).toBe('test'); @@ -152,7 +159,7 @@ describe('HTMLScriptElement', () => { }); it('Evaluates the text content as code when inserted before an element that is connected to the document.', () => { - const element = document.createElement('script'); + const element = document.createElement('script'); const div1 = document.createElement('div'); const div2 = document.createElement('div'); @@ -276,7 +283,7 @@ describe('HTMLScriptElement', () => { it('Does not evaluate types that are not supported.', () => { const div = document.createElement('div'); - const element = document.createElement('script'); + const element = document.createElement('script'); element.type = 'application/json'; element.textContent = '{"key": "value"}'; div.appendChild(element); @@ -299,7 +306,7 @@ describe('HTMLScriptElement', () => { it('Does not evaluate code if the element is not connected to DOM.', () => { const div = document.createElement('div'); - const element = document.createElement('script'); + const element = document.createElement('script'); element.text = 'window.test = "test";'; div.appendChild(element); expect(window['test']).toBe(undefined); @@ -317,7 +324,7 @@ 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'); + const element = document.createElement('script'); vi.spyOn(ResourceFetch.prototype, 'fetchSync').mockImplementation( () => 'globalThis.testFetch = "test";' @@ -333,7 +340,7 @@ describe('HTMLScriptElement', () => { }); 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'); + const element = document.createElement('script'); vi.spyOn(ResourceFetch.prototype, 'fetchSync').mockImplementation( () => 'globalThis.testFetch = "test";' @@ -488,7 +495,7 @@ describe('HTMLScriptElement', () => { }); it('Triggers an error event on Window when appending an element that contains invalid Javascript.', () => { - const element = document.createElement('script'); + const element = document.createElement('script'); let errorEvent: ErrorEvent | null = null; window.addEventListener('error', (event) => (errorEvent = event)); @@ -513,7 +520,7 @@ describe('HTMLScriptElement', () => { }); document = window.document; - const element = document.createElement('script'); + const element = document.createElement('script'); element.text = 'globalThis.test = /;'; @@ -528,7 +535,7 @@ describe('HTMLScriptElement', () => { }); document = window.document; - const element = document.createElement('script'); + const element = document.createElement('script'); element.text = 'globalThis.test = /;';