diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts index e87276f6..bd3e21f8 100644 --- a/packages/happy-dom/src/nodes/node/Node.ts +++ b/packages/happy-dom/src/nodes/node/Node.ts @@ -471,14 +471,20 @@ export default class Node extends EventTarget { * @returns Appended node. */ public [PropertySymbol.appendChild](node: Node, disableValidations = false): Node { + if (node[PropertySymbol.proxy]) { + node = node[PropertySymbol.proxy]; + } + + const self = this[PropertySymbol.proxy] || this; + if (!disableValidations) { - if (node === this) { + if (node === self) { throw new this[PropertySymbol.window].DOMException( "Failed to execute 'appendChild' on 'Node': Not possible to append a node as a child of itself." ); } - if (NodeUtility.isInclusiveAncestor(node, this, true)) { + if (NodeUtility.isInclusiveAncestor(node, self, true)) { throw new this[PropertySymbol.window].DOMException( "Failed to execute 'appendChild' on 'Node': The new node is a parent of the node to insert to.", DOMExceptionNameEnum.domException @@ -501,7 +507,7 @@ export default class Node extends EventTarget { node[PropertySymbol.parentNode][PropertySymbol.removeChild](node); } - node[PropertySymbol.parentNode] = this[PropertySymbol.proxy] || this; + node[PropertySymbol.parentNode] = self; node[PropertySymbol.clearCache](); @@ -522,7 +528,7 @@ export default class Node extends EventTarget { this[PropertySymbol.reportMutation]( new MutationRecord({ - target: this, + target: self, type: MutationTypeEnum.childList, addedNodes: [node] }) @@ -538,6 +544,10 @@ export default class Node extends EventTarget { * @returns Removed node. */ public [PropertySymbol.removeChild](node: Node): Node { + if (node[PropertySymbol.proxy]) { + node = node[PropertySymbol.proxy]; + } + node[PropertySymbol.parentNode] = null; node[PropertySymbol.clearCache](); @@ -578,7 +588,7 @@ export default class Node extends EventTarget { this[PropertySymbol.reportMutation]( new MutationRecord({ - target: this, + target: this[PropertySymbol.proxy] || this, type: MutationTypeEnum.childList, removedNodes: [node] }) @@ -600,18 +610,28 @@ export default class Node extends EventTarget { referenceNode: Node | null, disableValidations = false ): Node { + if (newNode[PropertySymbol.proxy]) { + newNode = newNode[PropertySymbol.proxy]; + } + + if (referenceNode && referenceNode[PropertySymbol.proxy]) { + referenceNode = referenceNode[PropertySymbol.proxy]; + } + if (newNode === referenceNode) { return newNode; } + const self = this[PropertySymbol.proxy] || this; + if (!disableValidations) { - if (newNode === this) { + if (newNode === self) { throw new this[PropertySymbol.window].DOMException( "Failed to execute 'insertBefore' on 'Node': Not possible to insert a node as a child of itself." ); } - if (NodeUtility.isInclusiveAncestor(newNode, this, true)) { + if (NodeUtility.isInclusiveAncestor(newNode, self, true)) { throw new this[PropertySymbol.window].DOMException( "Failed to execute 'insertBefore' on 'Node': The new node is a parent of the node to insert to.", DOMExceptionNameEnum.domException @@ -649,7 +669,7 @@ export default class Node extends EventTarget { newNode[PropertySymbol.parentNode][PropertySymbol.removeChild](newNode); } - newNode[PropertySymbol.parentNode] = this[PropertySymbol.proxy] || this; + newNode[PropertySymbol.parentNode] = self; newNode[PropertySymbol.clearCache](); @@ -687,7 +707,7 @@ export default class Node extends EventTarget { this[PropertySymbol.reportMutation]( new MutationRecord({ - target: this, + target: self, type: MutationTypeEnum.childList, addedNodes: [newNode] }) diff --git a/packages/happy-dom/test/nodes/html-form-element/HTMLFormElement.test.ts b/packages/happy-dom/test/nodes/html-form-element/HTMLFormElement.test.ts index 4dfb655f..fd3f6fca 100644 --- a/packages/happy-dom/test/nodes/html-form-element/HTMLFormElement.test.ts +++ b/packages/happy-dom/test/nodes/html-form-element/HTMLFormElement.test.ts @@ -1197,6 +1197,102 @@ describe('HTMLFormElement', () => { }); }); + describe('remove()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
Foo
'; + + const form = document.querySelector('form'); + + form.remove(); + + expect(document.body.children[0].children.length).toBe(0); + }); + }); + + describe('replaceWith()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
Foo
'; + + const form = document.querySelector('form'); + + form.replaceWith(document.createElement('div')); + + expect(document.body.children[0].children[0].tagName).toBe('DIV'); + }); + }); + + describe('before()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
Foo
'; + + const form = document.querySelector('form'); + + form.before(document.createElement('div')); + + expect(document.body.children[0].children[0].tagName).toBe('DIV'); + }); + }); + + describe('after()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
Foo
'; + + const form = document.querySelector('form'); + + form.after(document.createElement('div')); + + expect(document.body.children[0].children[1].tagName).toBe('DIV'); + }); + }); + + describe('append()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
Foo
'; + + const form = document.querySelector('form'); + + form.append(document.createElement('div')); + + expect(form.children[0].tagName).toBe('DIV'); + }); + }); + + describe('prepend()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
Foo
'; + + const form = document.querySelector('form'); + + form.prepend(document.createElement('div')); + + expect(form.children[0].tagName).toBe('DIV'); + }); + }); + + describe('replaceChildren()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
Foo
'; + + const form = document.querySelector('form'); + + form.replaceChildren(document.createElement('div')); + + expect(form.children[0].tagName).toBe('DIV'); + }); + }); + + describe('insertAdjacentElement()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
Foo
'; + + const form = document.querySelector('form'); + + form.insertAdjacentElement('beforebegin', document.createElement('div')); + + expect(document.body.children[0].children[0].tagName).toBe('DIV'); + }); + }); + for (const method of ['checkValidity', 'reportValidity']) { describe(`${method}()`, () => { it('Validates the form.', () => { diff --git a/packages/happy-dom/test/nodes/html-select-element/HTMLSelectElement.test.ts b/packages/happy-dom/test/nodes/html-select-element/HTMLSelectElement.test.ts index 3c26379f..7a189934 100644 --- a/packages/happy-dom/test/nodes/html-select-element/HTMLSelectElement.test.ts +++ b/packages/happy-dom/test/nodes/html-select-element/HTMLSelectElement.test.ts @@ -606,6 +606,105 @@ describe('HTMLSelectElement', () => { }); }); + describe('remove()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
'; + + const select = document.querySelector('select'); + + select.remove(); + + expect(document.body.children[0].children.length).toBe(0); + }); + }); + + describe('replaceWith()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
'; + + const select = document.querySelector('select'); + + select.replaceWith(document.createElement('div')); + + expect(document.body.children[0].children[0].tagName).toBe('DIV'); + }); + }); + + describe('before()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
'; + + const select = document.querySelector('select'); + + select.before(document.createElement('div')); + + expect(document.body.children[0].children[0].tagName).toBe('DIV'); + }); + }); + + describe('after()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
'; + + const select = document.querySelector('select'); + + select.after(document.createElement('div')); + + expect(document.body.children[0].children[1].tagName).toBe('DIV'); + }); + }); + + describe('append()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
'; + + const select = document.querySelector('select'); + const newOption = document.createElement('option'); + + select.append(newOption); + + expect(select.children[1]).toBe(newOption); + }); + }); + + describe('prepend()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
'; + + const select = document.querySelector('select'); + const newOption = document.createElement('option'); + + select.prepend(newOption); + + expect(select.children[0]).toBe(newOption); + }); + }); + + describe('replaceChildren()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
'; + + const select = document.querySelector('select'); + const newOption = document.createElement('option'); + + select.replaceChildren(newOption); + + expect(select.children[0]).toBe(newOption); + }); + }); + + describe('insertAdjacentElement()', () => { + it('Sets "parentNode" of child elements to the proxy and not the original element.', () => { + document.body.innerHTML = '
'; + + const select = document.querySelector('select'); + + select.insertAdjacentElement('beforebegin', document.createElement('div')); + + expect(document.body.children[0].children[0].tagName).toBe('DIV'); + }); + }); + describe('setCustomValidity()', () => { it('Returns validation message.', () => { element.setCustomValidity('Error message');