From 68ef1e53d0273b56f13f62d96358d760b1d8c6a2 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Sun, 7 Apr 2024 17:17:19 +0200 Subject: [PATCH] fix: [#1392] Adds support for using Node.prototype.cloneNode.call(element), Node.prototype.appendChild.call(element), Node.prototype.removeChild.call(element), Node.prototype.insertBefore.call(element) and Node.prototype.replaceChild.call(element) --- packages/happy-dom/src/PropertySymbol.ts | 5 ++ .../src/nodes/character-data/CharacterData.ts | 9 +-- .../happy-dom/src/nodes/comment/Comment.ts | 9 +-- .../document-fragment/DocumentFragment.ts | 21 ++---- .../src/nodes/document-type/DocumentType.ts | 9 +-- .../happy-dom/src/nodes/document/Document.ts | 21 ++---- .../happy-dom/src/nodes/element/Element.ts | 21 ++---- .../html-base-element/HTMLBaseElement.ts | 9 +-- .../src/nodes/html-element/HTMLElement.ts | 7 +- .../html-form-element/HTMLFormElement.ts | 11 ++- .../html-iframe-element/HTMLIFrameElement.ts | 12 +-- .../html-image-element/HTMLImageElement.ts | 9 +-- .../html-input-element/HTMLInputElement.ts | 11 ++- .../html-label-element/HTMLLabelElement.ts | 11 ++- .../html-media-element/HTMLMediaElement.ts | 15 ++-- .../html-script-element/HTMLScriptElement.ts | 11 ++- .../html-slot-element/HTMLSlotElement.ts | 11 ++- .../html-style-element/HTMLStyleElement.ts | 12 +-- .../HTMLTemplateElement.ts | 17 +++-- .../HTMLTextAreaElement.ts | 10 +-- packages/happy-dom/src/nodes/node/Node.ts | 70 +++++++++++++++--- .../src/nodes/shadow-root/ShadowRoot.ts | 11 ++- .../src/nodes/svg-element/SVGSVGElement.ts | 12 +-- packages/happy-dom/src/nodes/text/Text.ts | 9 +-- .../happy-dom/test/nodes/node/Node.test.ts | 73 +++++++++++++++++++ 25 files changed, 247 insertions(+), 169 deletions(-) diff --git a/packages/happy-dom/src/PropertySymbol.ts b/packages/happy-dom/src/PropertySymbol.ts index e3b7fc157..81e22cf21 100644 --- a/packages/happy-dom/src/PropertySymbol.ts +++ b/packages/happy-dom/src/PropertySymbol.ts @@ -159,3 +159,8 @@ export const screen = Symbol('screen'); export const sessionStorage = Symbol('sessionStorage'); export const localStorage = Symbol('localStorage'); export const sandbox = Symbol('sandbox'); +export const cloneNode = Symbol('cloneNode'); +export const appendChild = Symbol('appendChild'); +export const removeChild = Symbol('removeChild'); +export const insertBefore = Symbol('insertBefore'); +export const replaceChild = Symbol('replaceChild'); diff --git a/packages/happy-dom/src/nodes/character-data/CharacterData.ts b/packages/happy-dom/src/nodes/character-data/CharacterData.ts index c21f4de5d..215fab7f0 100644 --- a/packages/happy-dom/src/nodes/character-data/CharacterData.ts +++ b/packages/happy-dom/src/nodes/character-data/CharacterData.ts @@ -20,6 +20,7 @@ export default abstract class CharacterData implements IChildNode, INonDocumentTypeChildNode { public [PropertySymbol.data] = ''; + public cloneNode: (deep?: boolean) => CharacterData; /** * Constructor. @@ -220,14 +221,10 @@ export default abstract class CharacterData } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): CharacterData { - const clone = super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): CharacterData { + const clone = super[PropertySymbol.cloneNode](deep); clone[PropertySymbol.data] = this[PropertySymbol.data]; return clone; } diff --git a/packages/happy-dom/src/nodes/comment/Comment.ts b/packages/happy-dom/src/nodes/comment/Comment.ts index 2c46703b2..7128b4a90 100644 --- a/packages/happy-dom/src/nodes/comment/Comment.ts +++ b/packages/happy-dom/src/nodes/comment/Comment.ts @@ -7,6 +7,7 @@ import NodeTypeEnum from '../node/NodeTypeEnum.js'; */ export default class Comment extends CharacterData { public [PropertySymbol.nodeType] = NodeTypeEnum.commentNode; + public cloneNode: (deep?: boolean) => Comment; /** * Node name. @@ -27,13 +28,9 @@ export default class Comment extends CharacterData { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): Comment { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): Comment { + return super[PropertySymbol.cloneNode](deep); } } diff --git a/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts b/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts index 33f7056ab..ffa1f7202 100644 --- a/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts +++ b/packages/happy-dom/src/nodes/document-fragment/DocumentFragment.ts @@ -17,6 +17,7 @@ export default class DocumentFragment extends Node { public readonly [PropertySymbol.children]: HTMLCollection = new HTMLCollection(); public [PropertySymbol.rootNode]: Node = this; public [PropertySymbol.nodeType] = NodeTypeEnum.documentFragmentNode; + public cloneNode: (deep?: boolean) => DocumentFragment; /** * Returns the document fragment children. @@ -198,14 +199,10 @@ export default class DocumentFragment extends Node { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): DocumentFragment { - const clone = super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): DocumentFragment { + const clone = super[PropertySymbol.cloneNode](deep); if (deep) { for (const node of clone[PropertySymbol.childNodes]) { @@ -221,7 +218,7 @@ export default class DocumentFragment extends Node { /** * @override */ - public override appendChild(node: Node): Node { + public override [PropertySymbol.appendChild](node: Node): Node { // We do not call super here as this will be handled by ElementUtility to improve performance by avoiding validation and other checks. return ElementUtility.appendChild(this, node); } @@ -229,7 +226,7 @@ export default class DocumentFragment extends Node { /** * @override */ - public override removeChild(node: Node): Node { + public override [PropertySymbol.removeChild](node: Node): Node { // We do not call super here as this will be handled by ElementUtility to improve performance by avoiding validation and other checks. return ElementUtility.removeChild(this, node); } @@ -237,13 +234,7 @@ export default class DocumentFragment extends Node { /** * @override */ - public override insertBefore(newNode: Node, referenceNode: Node | null): Node { - if (arguments.length < 2) { - throw new TypeError( - `Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only ${arguments.length} present.` - ); - } - + public override [PropertySymbol.insertBefore](newNode: Node, referenceNode: Node | null): Node { // We do not call super here as this will be handled by ElementUtility to improve performance by avoiding validation and other checks. return ElementUtility.insertBefore(this, newNode, referenceNode); } diff --git a/packages/happy-dom/src/nodes/document-type/DocumentType.ts b/packages/happy-dom/src/nodes/document-type/DocumentType.ts index 13244147e..fa4c0bfaf 100644 --- a/packages/happy-dom/src/nodes/document-type/DocumentType.ts +++ b/packages/happy-dom/src/nodes/document-type/DocumentType.ts @@ -10,6 +10,7 @@ export default class DocumentType extends Node { public [PropertySymbol.name] = ''; public [PropertySymbol.publicId] = ''; public [PropertySymbol.systemId] = ''; + public cloneNode: (deep?: boolean) => DocumentType; /** * Returns name. @@ -57,14 +58,10 @@ export default class DocumentType extends Node { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): DocumentType { - const clone = super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): DocumentType { + const clone = super[PropertySymbol.cloneNode](deep); clone[PropertySymbol.name] = this[PropertySymbol.name]; clone[PropertySymbol.publicId] = this[PropertySymbol.publicId]; clone[PropertySymbol.systemId] = this[PropertySymbol.systemId]; diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts index 3e11866c4..44c0b560a 100644 --- a/packages/happy-dom/src/nodes/document/Document.ts +++ b/packages/happy-dom/src/nodes/document/Document.ts @@ -69,6 +69,7 @@ export default class Document extends Node { public [PropertySymbol.referrer] = ''; public [PropertySymbol.defaultView]: BrowserWindow | null = null; public [PropertySymbol.ownerWindow]: BrowserWindow; + public cloneNode: (deep?: boolean) => Document; // Private properties #selection: Selection = null; @@ -800,14 +801,10 @@ export default class Document extends Node { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): Document { - const clone = super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): Document { + const clone = super[PropertySymbol.cloneNode](deep); if (deep) { for (const node of clone[PropertySymbol.childNodes]) { @@ -823,7 +820,7 @@ export default class Document extends Node { /** * @override */ - public override appendChild(node: Node): Node { + public override [PropertySymbol.appendChild](node: Node): Node { // We do not call super here as this will be handled by ElementUtility to improve performance by avoiding validation and other checks. return ElementUtility.appendChild(this, node); } @@ -831,7 +828,7 @@ export default class Document extends Node { /** * @override */ - public override removeChild(node: Node): Node { + public override [PropertySymbol.removeChild](node: Node): Node { // We do not call super here as this will be handled by ElementUtility to improve performance by avoiding validation and other checks. return ElementUtility.removeChild(this, node); } @@ -839,13 +836,7 @@ export default class Document extends Node { /** * @override */ - public override insertBefore(newNode: Node, referenceNode: Node | null): Node { - if (arguments.length < 2) { - throw new TypeError( - `Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only ${arguments.length} present.` - ); - } - + public override [PropertySymbol.insertBefore](newNode: Node, referenceNode: Node | null): Node { // We do not call super here as this will be handled by ElementUtility to improve performance by avoiding validation and other checks. return ElementUtility.insertBefore(this, newNode, referenceNode); } diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts index f34da1086..2cb1f56d4 100644 --- a/packages/happy-dom/src/nodes/element/Element.ts +++ b/packages/happy-dom/src/nodes/element/Element.ts @@ -49,6 +49,7 @@ export default class Element public static [PropertySymbol.localName]: string | null = null; public static [PropertySymbol.namespaceURI]: string | null = null; public static observedAttributes: string[]; + public cloneNode: (deep?: boolean) => Element; // Events public oncancel: (event: Event) => void | null = null; @@ -464,14 +465,10 @@ export default class Element } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): Element { - const clone = super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): Element { + const clone = super[PropertySymbol.cloneNode](deep); clone[PropertySymbol.tagName] = this[PropertySymbol.tagName]; clone[PropertySymbol.localName] = this[PropertySymbol.localName]; @@ -504,7 +501,7 @@ export default class Element /** * @override */ - public override appendChild(node: Node): Node { + public override [PropertySymbol.appendChild](node: Node): Node { // We do not call super here as this will be handled by ElementUtility to improve performance by avoiding validation and other checks. return ElementUtility.appendChild(this, node); } @@ -512,7 +509,7 @@ export default class Element /** * @override */ - public override removeChild(node: Node): Node { + public override [PropertySymbol.removeChild](node: Node): Node { // We do not call super here as this will be handled by ElementUtility to improve performance by avoiding validation and other checks. return ElementUtility.removeChild(this, node); } @@ -520,13 +517,7 @@ export default class Element /** * @override */ - public override insertBefore(newNode: Node, referenceNode: Node | null): Node { - if (arguments.length < 2) { - throw new TypeError( - `Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only ${arguments.length} present.` - ); - } - + public override [PropertySymbol.insertBefore](newNode: Node, referenceNode: Node | null): Node { // We do not call super here as this will be handled by ElementUtility to improve performance by avoiding validation and other checks. return ElementUtility.insertBefore(this, newNode, referenceNode); } diff --git a/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts b/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts index 39cc88747..b33b3c721 100644 --- a/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts +++ b/packages/happy-dom/src/nodes/html-base-element/HTMLBaseElement.ts @@ -8,6 +8,7 @@ import * as PropertySymbol from '../../PropertySymbol.js'; * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base. */ export default class HTMLBaseElement extends HTMLElement { + public cloneNode: (deep?: boolean) => HTMLBaseElement; /** * Returns href. * @@ -49,13 +50,9 @@ export default class HTMLBaseElement extends HTMLElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): HTMLBaseElement { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLBaseElement { + return super[PropertySymbol.cloneNode](deep); } } diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts index b553de173..6022e22a9 100644 --- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts +++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts @@ -21,6 +21,9 @@ import IDataset from '../element/IDataset.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement. */ export default class HTMLElement extends Element { + // Public properties + public cloneNode: (deep?: boolean) => HTMLElement; + // Events public oncopy: (event: Event) => void | null = null; public oncut: (event: Event) => void | null = null; @@ -486,8 +489,8 @@ export default class HTMLElement extends Element { /** * @override */ - public cloneNode(deep = false): HTMLElement { - const clone = super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLElement { + const clone = super[PropertySymbol.cloneNode](deep); clone[PropertySymbol.accessKey] = this[PropertySymbol.accessKey]; clone[PropertySymbol.contentEditable] = this[PropertySymbol.contentEditable]; 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 576fe72e0..553de7bfd 100644 --- a/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts +++ b/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts @@ -21,6 +21,9 @@ import BrowserWindow from '../../window/BrowserWindow.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement. */ export default class HTMLFormElement extends HTMLElement { + // Public properties + public cloneNode: (deep?: boolean) => HTMLFormElement; + // Internal properties. public [PropertySymbol.elements]: HTMLFormControlsCollection = new HTMLFormControlsCollection(); public [PropertySymbol.length] = 0; @@ -325,14 +328,10 @@ export default class HTMLFormElement extends HTMLElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): HTMLFormElement { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLFormElement { + return super[PropertySymbol.cloneNode](deep); } /** diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts index d43fb63e8..11b00bf00 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts @@ -18,6 +18,9 @@ import DOMTokenList from '../../dom-token-list/DOMTokenList.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement. */ export default class HTMLIFrameElement extends HTMLElement { + // Public properties + public cloneNode: (deep?: boolean) => HTMLIFrameElement; + // Events public onload: (event: Event) => void | null = null; public onerror: (event: Event) => void | null = null; @@ -25,6 +28,7 @@ export default class HTMLIFrameElement extends HTMLElement { // Internal properties public override [PropertySymbol.attributes]: NamedNodeMap; public [PropertySymbol.sandbox]: DOMTokenList = null; + // Private properties #contentWindowContainer: { window: BrowserWindow | CrossOriginBrowserWindow | null } = { window: null @@ -226,13 +230,9 @@ export default class HTMLIFrameElement extends HTMLElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): HTMLIFrameElement { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLIFrameElement { + return super[PropertySymbol.cloneNode](deep); } } 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 d09b5a609..aa59e3f94 100644 --- a/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts +++ b/packages/happy-dom/src/nodes/html-image-element/HTMLImageElement.ts @@ -15,6 +15,7 @@ export default class HTMLImageElement extends HTMLElement { public [PropertySymbol.loading] = 'auto'; public [PropertySymbol.x] = 0; public [PropertySymbol.y] = 0; + public cloneNode: (deep?: boolean) => HTMLImageElement; /** * Returns complete. @@ -310,13 +311,9 @@ export default class HTMLImageElement extends HTMLElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): HTMLImageElement { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLImageElement { + return super[PropertySymbol.cloneNode](deep); } } 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 c9dceb345..566293ce0 100644 --- a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts +++ b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts @@ -31,6 +31,9 @@ import { URL } from 'url'; * https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/nodes/nodes/HTMLInputElement-impl.js (MIT licensed). */ export default class HTMLInputElement extends HTMLElement { + // Public properties + public cloneNode: (deep?: boolean) => HTMLInputElement; + // Events public oninput: (event: Event) => void | null = null; public oninvalid: (event: Event) => void | null = null; @@ -1278,14 +1281,10 @@ export default class HTMLInputElement extends HTMLElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): HTMLInputElement { - const clone = super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLInputElement { + const clone = super[PropertySymbol.cloneNode](deep); clone.formAction = this.formAction; clone.formMethod = this.formMethod; clone[PropertySymbol.value] = this[PropertySymbol.value]; diff --git a/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElement.ts b/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElement.ts index be83da6e7..74833b03d 100644 --- a/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElement.ts +++ b/packages/happy-dom/src/nodes/html-label-element/HTMLLabelElement.ts @@ -12,6 +12,9 @@ import PointerEvent from '../../event/events/PointerEvent.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement. */ export default class HTMLLabelElement extends HTMLElement { + // Public properties + public cloneNode: (deep?: boolean) => HTMLLabelElement; + /** * Returns a string containing the ID of the labeled control. This reflects the "for" attribute. * @@ -60,14 +63,10 @@ export default class HTMLLabelElement extends HTMLElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): HTMLLabelElement { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLLabelElement { + return super[PropertySymbol.cloneNode](deep); } /** diff --git a/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts b/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts index 45467cb63..0c0edfd2c 100644 --- a/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts +++ b/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts @@ -19,6 +19,9 @@ interface IMediaError { * */ export default class HTMLMediaElement extends HTMLElement { + // Public properties + public cloneNode: (deep?: boolean) => HTMLMediaElement; + // Events public onabort: (event: Event) => void | null = null; public oncanplay: (event: Event) => void | null = null; @@ -528,17 +531,9 @@ export default class HTMLMediaElement extends HTMLElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. - */ - /** - * - * @param deep */ - public cloneNode(deep = false): HTMLMediaElement { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLMediaElement { + return super[PropertySymbol.cloneNode](deep); } } 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 8d92d9e17..aa2ca7381 100644 --- a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts +++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts @@ -18,6 +18,9 @@ import BrowserErrorCaptureEnum from '../../browser/enums/BrowserErrorCaptureEnum * https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement. */ export default class HTMLScriptElement extends HTMLElement { + // Public properties + public cloneNode: (deep?: boolean) => HTMLScriptElement; + // Events public onerror: (event: ErrorEvent) => void = null; public onload: (event: Event) => void = null; @@ -189,14 +192,10 @@ export default class HTMLScriptElement extends HTMLElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): HTMLScriptElement { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLScriptElement { + return super[PropertySymbol.cloneNode](deep); } /** diff --git a/packages/happy-dom/src/nodes/html-slot-element/HTMLSlotElement.ts b/packages/happy-dom/src/nodes/html-slot-element/HTMLSlotElement.ts index 70833640b..123a8e3a8 100644 --- a/packages/happy-dom/src/nodes/html-slot-element/HTMLSlotElement.ts +++ b/packages/happy-dom/src/nodes/html-slot-element/HTMLSlotElement.ts @@ -13,6 +13,9 @@ import Event from '../../event/Event.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement. */ export default class HTMLSlotElement extends HTMLElement { + // Public properties + public cloneNode: (deep?: boolean) => HTMLSlotElement; + // Events public onslotchange: (event: Event) => void | null = null; @@ -102,13 +105,9 @@ export default class HTMLSlotElement extends HTMLElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): HTMLSlotElement { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLSlotElement { + return super[PropertySymbol.cloneNode](deep); } } diff --git a/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts b/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts index 6cdbe9584..2d0ec33e0 100644 --- a/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts +++ b/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts @@ -82,8 +82,8 @@ export default class HTMLStyleElement extends HTMLElement { /** * @override */ - public override appendChild(node: Node): Node { - const returnValue = super.appendChild(node); + public override [PropertySymbol.appendChild](node: Node): Node { + const returnValue = super[PropertySymbol.appendChild](node); if (this[PropertySymbol.sheet]) { this[PropertySymbol.sheet].replaceSync(this.textContent); } @@ -93,8 +93,8 @@ export default class HTMLStyleElement extends HTMLElement { /** * @override */ - public override removeChild(node: Node): Node { - const returnValue = super.removeChild(node); + public override [PropertySymbol.removeChild](node: Node): Node { + const returnValue = super[PropertySymbol.removeChild](node); if (this[PropertySymbol.sheet]) { this[PropertySymbol.sheet].replaceSync(this.textContent); } @@ -104,8 +104,8 @@ export default class HTMLStyleElement extends HTMLElement { /** * @override */ - public override insertBefore(newNode: Node, referenceNode: Node | null): Node { - const returnValue = super.insertBefore(newNode, referenceNode); + public override [PropertySymbol.insertBefore](newNode: Node, referenceNode: Node | null): Node { + const returnValue = super[PropertySymbol.insertBefore](newNode, referenceNode); if (this[PropertySymbol.sheet]) { this[PropertySymbol.sheet].replaceSync(this.textContent); } diff --git a/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts b/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts index 8fc0478e6..7b912a0c6 100644 --- a/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts +++ b/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts @@ -12,6 +12,9 @@ import XMLParser from '../../xml-parser/XMLParser.js'; * https://developer.mozilla.org/en-US/docs/Web/API/HTMLTemplateElement. */ export default class HTMLTemplateElement extends HTMLElement { + // Public properties + public cloneNode: (deep?: boolean) => HTMLTemplateElement; + // Internal properties public [PropertySymbol.content]: DocumentFragment = this[PropertySymbol.ownerDocument].createDocumentFragment(); @@ -80,37 +83,37 @@ export default class HTMLTemplateElement extends HTMLElement { /** * @override */ - public appendChild(node: Node): Node { + public override [PropertySymbol.appendChild](node: Node): Node { return this[PropertySymbol.content].appendChild(node); } /** * @override */ - public removeChild(node: Node): Node { + public override [PropertySymbol.removeChild](node: Node): Node { return this[PropertySymbol.content].removeChild(node); } /** * @override */ - public insertBefore(newNode: Node, referenceNode: Node): Node { + public override [PropertySymbol.insertBefore](newNode: Node, referenceNode: Node): Node { return this[PropertySymbol.content].insertBefore(newNode, referenceNode); } /** * @override */ - public replaceChild(newChild: Node, oldChild: Node): Node { + public override [PropertySymbol.replaceChild](newChild: Node, oldChild: Node): Node { return this[PropertySymbol.content].replaceChild(newChild, oldChild); } /** * @override */ - public cloneNode(deep = false): HTMLTemplateElement { - const clone = super.cloneNode(deep); - clone[PropertySymbol.content] = this[PropertySymbol.content].cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLTemplateElement { + const clone = super[PropertySymbol.cloneNode](deep); + clone[PropertySymbol.content] = this[PropertySymbol.content].cloneNode(deep); return clone; } } diff --git a/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElement.ts b/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElement.ts index f34adedad..8fc7ccc36 100644 --- a/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElement.ts +++ b/packages/happy-dom/src/nodes/html-text-area-element/HTMLTextAreaElement.ts @@ -21,6 +21,8 @@ import HTMLTextAreaElementNamedNodeMap from './HTMLTextAreaElementNamedNodeMap.j * https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement. */ export default class HTMLTextAreaElement extends HTMLElement { + // Public properties + public cloneNode: (deep?: boolean) => HTMLTextAreaElement; public readonly type = 'textarea'; // Events @@ -566,14 +568,10 @@ export default class HTMLTextAreaElement extends HTMLElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): HTMLTextAreaElement { - const clone = super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): HTMLTextAreaElement { + const clone = super[PropertySymbol.cloneNode](deep); clone[PropertySymbol.value] = this[PropertySymbol.value]; clone.#selectionStart = this.#selectionStart; diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts index 68c9eb2f4..98094c4b9 100644 --- a/packages/happy-dom/src/nodes/node/Node.ts +++ b/packages/happy-dom/src/nodes/node/Node.ts @@ -328,6 +328,63 @@ export default class Node extends EventTarget { * @returns Cloned node. */ public cloneNode(deep = false): Node { + return this[PropertySymbol.cloneNode](deep); + } + + /** + * Append a child node to childNodes. + * + * @param node Node to append. + * @returns Appended node. + */ + public appendChild(node: Node): Node { + return this[PropertySymbol.appendChild](node); + } + + /** + * Remove Child element from childNodes array. + * + * @param node Node to remove. + * @returns Removed node. + */ + public removeChild(node: Node): Node { + return this[PropertySymbol.removeChild](node); + } + + /** + * Inserts a node before another. + * + * @param newNode Node to insert. + * @param referenceNode Node to insert before. + * @returns Inserted node. + */ + public insertBefore(newNode: Node, referenceNode: Node | null): Node { + if (arguments.length < 2) { + throw new TypeError( + `Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only ${arguments.length} present.` + ); + } + return this[PropertySymbol.insertBefore](newNode, referenceNode); + } + + /** + * Replaces a node with another. + * + * @param newChild New child. + * @param oldChild Old child. + * @returns Replaced node. + */ + public replaceChild(newChild: Node, oldChild: Node): Node { + return this[PropertySymbol.replaceChild](newChild, oldChild); + } + + /** + * Clones a node. + * + * @param [deep=false] "true" to clone deep. + * @returns Cloned node. + */ + public [PropertySymbol.cloneNode](deep = false): Node { const clone = NodeFactory.createNode( this[PropertySymbol.ownerDocument], this.constructor @@ -357,7 +414,7 @@ export default class Node extends EventTarget { * @param node Node to append. * @returns Appended node. */ - public appendChild(node: Node): Node { + public [PropertySymbol.appendChild](node: Node): Node { return NodeUtility.appendChild(this, node); } @@ -367,7 +424,7 @@ export default class Node extends EventTarget { * @param node Node to remove. * @returns Removed node. */ - public removeChild(node: Node): Node { + public [PropertySymbol.removeChild](node: Node): Node { return NodeUtility.removeChild(this, node); } @@ -378,12 +435,7 @@ export default class Node extends EventTarget { * @param referenceNode Node to insert before. * @returns Inserted node. */ - public insertBefore(newNode: Node, referenceNode: Node | null): Node { - if (arguments.length < 2) { - throw new TypeError( - `Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only ${arguments.length} present.` - ); - } + public [PropertySymbol.insertBefore](newNode: Node, referenceNode: Node | null): Node { return NodeUtility.insertBefore(this, newNode, referenceNode); } @@ -394,7 +446,7 @@ export default class Node extends EventTarget { * @param oldChild Old child. * @returns Replaced node. */ - public replaceChild(newChild: Node, oldChild: Node): Node { + public [PropertySymbol.replaceChild](newChild: Node, oldChild: Node): Node { this.insertBefore(newChild, oldChild); this.removeChild(oldChild); diff --git a/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts b/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts index 6e557d741..fdb7dad82 100644 --- a/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts +++ b/packages/happy-dom/src/nodes/shadow-root/ShadowRoot.ts @@ -12,6 +12,9 @@ import SVGElement from '../svg-element/SVGElement.js'; * ShadowRoot. */ export default class ShadowRoot extends DocumentFragment { + // Public properties + public cloneNode: (deep?: boolean) => ShadowRoot; + // Events public onslotchange: (event: Event) => void | null = null; @@ -113,14 +116,10 @@ export default class ShadowRoot extends DocumentFragment { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): ShadowRoot { - const clone = super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): ShadowRoot { + const clone = super[PropertySymbol.cloneNode](deep); clone[PropertySymbol.mode] = this.mode; return clone; } diff --git a/packages/happy-dom/src/nodes/svg-element/SVGSVGElement.ts b/packages/happy-dom/src/nodes/svg-element/SVGSVGElement.ts index c56a65b54..0d0abd61f 100644 --- a/packages/happy-dom/src/nodes/svg-element/SVGSVGElement.ts +++ b/packages/happy-dom/src/nodes/svg-element/SVGSVGElement.ts @@ -8,11 +8,15 @@ import SVGTransform from './SVGTransform.js'; import SVGAnimatedRect from './SVGAnimatedRect.js'; import Node from '../node/Node.js'; import Event from '../../event/Event.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * SVGSVGElement. */ export default class SVGSVGElement extends SVGGraphicsElement { + // Public properties + public cloneNode: (deep?: boolean) => SVGSVGElement; + // Events public onafterprint: (event: Event) => void | null = null; public onbeforeprint: (event: Event) => void | null = null; @@ -326,13 +330,9 @@ export default class SVGSVGElement extends SVGGraphicsElement { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): SVGSVGElement { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): SVGSVGElement { + return super[PropertySymbol.cloneNode](deep); } } diff --git a/packages/happy-dom/src/nodes/text/Text.ts b/packages/happy-dom/src/nodes/text/Text.ts index adabcc4ca..eb29afc3d 100644 --- a/packages/happy-dom/src/nodes/text/Text.ts +++ b/packages/happy-dom/src/nodes/text/Text.ts @@ -10,6 +10,7 @@ import NodeTypeEnum from '../node/NodeTypeEnum.js'; * Text node. */ export default class Text extends CharacterData { + public cloneNode: (deep?: boolean) => Text; public override [PropertySymbol.nodeType] = NodeTypeEnum.textNode; /** @@ -79,14 +80,10 @@ export default class Text extends CharacterData { } /** - * Clones a node. - * * @override - * @param [deep=false] "true" to clone deep. - * @returns Cloned node. */ - public cloneNode(deep = false): Text { - return super.cloneNode(deep); + public override [PropertySymbol.cloneNode](deep = false): Text { + return super[PropertySymbol.cloneNode](deep); } /** diff --git a/packages/happy-dom/test/nodes/node/Node.test.ts b/packages/happy-dom/test/nodes/node/Node.test.ts index 50ee6bf72..8a4efe804 100644 --- a/packages/happy-dom/test/nodes/node/Node.test.ts +++ b/packages/happy-dom/test/nodes/node/Node.test.ts @@ -422,6 +422,17 @@ describe('Node', () => { Array.from(clone.childNodes.filter((node) => node.nodeType === Node.ELEMENT_NODE)) ); }); + + it('Supports Node.prototype.cloneNode.call(element).', () => { + expect(Node.prototype.cloneNode).toBe(HTMLElement.prototype.cloneNode); + + const div = document.createElement('div'); + const clone = Node.prototype.cloneNode.call(div); + + expect(div.tagName).toBe(clone.tagName); + expect(div.localName).toBe(clone.localName); + expect(div.namespaceURI).toBe(clone.namespaceURI); + }); }); describe('appendChild()', () => { @@ -479,6 +490,18 @@ describe('Node', () => { ); } }); + + it('Supports Node.prototype.appendChild.call(element).', () => { + expect(Node.prototype.appendChild).toBe(HTMLElement.prototype.appendChild); + + const parent = document.createElement('div'); + const child = document.createElement('span'); + + Node.prototype.appendChild.call(parent, child); + + expect(parent.childNodes.length).toBe(1); + expect(parent.childNodes[0]).toBe(child); + }); }); describe('removeChild()', () => { @@ -503,6 +526,18 @@ describe('Node', () => { expect(child.isConnected).toBe(false); expect(removed).toEqual(child); }); + + it('Supports Node.prototype.removeChild.call(element).', () => { + expect(Node.prototype.removeChild).toBe(HTMLElement.prototype.removeChild); + + const parent = document.createElement('div'); + const child = document.createElement('span'); + + Node.prototype.appendChild.call(parent, child); + Node.prototype.removeChild.call(parent, child); + + expect(parent.childNodes.length).toBe(0); + }); }); describe('insertBefore()', () => { @@ -575,6 +610,7 @@ describe('Node', () => { parent.appendChild(child1); parent.appendChild(child2); + // @ts-expect-error expect(() => parent.insertBefore(newNode)).toThrow( "Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 1 present." ); @@ -590,6 +626,7 @@ describe('Node', () => { parent.appendChild(child1); parent.appendChild(child2); parent.insertBefore(newNode, null); + // @ts-expect-error parent.insertBefore(newNode1, undefined); expect(parent.childNodes[0]).toBe(child1); @@ -627,6 +664,21 @@ describe('Node', () => { ); } }); + + it('Supports Node.prototype.insertBefore.call(element).', () => { + expect(Node.prototype.insertBefore).toBe(HTMLElement.prototype.insertBefore); + + const parent = document.createElement('div'); + const referenceNode = document.createElement('span'); + const newNode = document.createElement('span'); + + Node.prototype.appendChild.call(parent, referenceNode); + Node.prototype.insertBefore.call(parent, newNode, referenceNode); + + expect(parent.childNodes.length).toBe(2); + expect(parent.childNodes[0]).toBe(newNode); + expect(parent.childNodes[1]).toBe(referenceNode); + }); }); describe('replaceChild()', () => { @@ -648,6 +700,27 @@ describe('Node', () => { expect(newNode.isConnected).toBe(true); }); + + it('Supports Node.prototype.replaceChild.call(element).', () => { + expect(Node.prototype.replaceChild).toBe(HTMLElement.prototype.replaceChild); + + const child1 = document.createElement('span'); + const child2 = document.createElement('span'); + const newNode = document.createElement('span'); + const parent = document.createElement('div'); + + parent.appendChild(child1); + parent.appendChild(child2); + Node.prototype.replaceChild.call(parent, newNode, child2); + + expect(newNode.parentNode).toBe(parent); + expect(Array.from(parent.childNodes)).toEqual([child1, newNode]); + expect(newNode.isConnected).toBe(false); + + document.body.appendChild(parent); + + expect(newNode.isConnected).toBe(true); + }); }); describe('toEqualNode()', () => {