Skip to content

Commit

Permalink
fix: [1533] Fixes incorrect handling of non-node items inserted using…
Browse files Browse the repository at this point in the history
… replaceWith(), before() and after() (#1533)

* Fix incorrect handling of non-node items inserted into the DOM

* Properly format appendChild() calls to make eslint happy

* Fix outerHTML setter and edit tests to match correct behavior of non-node insertions
  • Loading branch information
BenjaminAster committed Sep 11, 2024
1 parent afc3692 commit afd256b
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 101 deletions.
52 changes: 19 additions & 33 deletions packages/happy-dom/src/nodes/child-node/ChildNodeUtility.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import DOMException from '../../exception/DOMException.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import XMLParser from '../../xml-parser/XMLParser.js';
import DocumentFragment from '../document-fragment/DocumentFragment.js';
import Document from '../document/Document.js';
import Node from '../node/Node.js';
import IParentNode from '../parent-node/IParentNode.js';
import IChildNode from './IChildNode.js';
Expand Down Expand Up @@ -36,15 +33,13 @@ export default class ChildNodeUtility {
}

for (const node of nodes) {
if (typeof node === 'string') {
const newChildNodes = (<DocumentFragment>(
XMLParser.parse(<Document>childNode[PropertySymbol.ownerDocument], node)
))[PropertySymbol.nodeArray];
while (newChildNodes.length) {
parent.insertBefore(newChildNodes[0], childNode);
}
} else {
if (node instanceof Node) {
parent.insertBefore(node, childNode);
} else {
parent.insertBefore(
parent[PropertySymbol.ownerDocument].createTextNode(String(node)),
childNode
);
}
}

Expand All @@ -65,15 +60,13 @@ export default class ChildNodeUtility {
}

for (const node of nodes) {
if (typeof node === 'string') {
const newChildNodes = (<DocumentFragment>(
XMLParser.parse(<Document>childNode[PropertySymbol.ownerDocument], node)
))[PropertySymbol.nodeArray];
while (newChildNodes.length) {
parent.insertBefore(newChildNodes[0], childNode);
}
} else {
if (node instanceof Node) {
parent.insertBefore(node, childNode);
} else {
parent.insertBefore(
parent[PropertySymbol.ownerDocument].createTextNode(String(node)),
childNode
);
}
}
}
Expand All @@ -94,21 +87,14 @@ export default class ChildNodeUtility {
const nextSibling = childNode.nextSibling;

for (const node of nodes) {
if (typeof node === 'string') {
const newChildNodes = (<DocumentFragment>(
XMLParser.parse(<Document>childNode[PropertySymbol.ownerDocument], node)
))[PropertySymbol.nodeArray];
while (newChildNodes.length) {
if (!nextSibling) {
parent.appendChild(newChildNodes[0]);
} else {
parent.insertBefore(newChildNodes[0], nextSibling);
}
}
} else if (!nextSibling) {
parent.appendChild(node);
const insertedNode =
node instanceof Node
? node
: parent[PropertySymbol.ownerDocument].createTextNode(String(node));
if (!nextSibling) {
parent.appendChild(insertedNode);
} else {
parent.insertBefore(node, nextSibling);
parent.insertBefore(insertedNode, nextSibling);
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion packages/happy-dom/src/nodes/element/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,10 @@ export default class Element
* @param html HTML.
*/
public set outerHTML(html: string) {
this.replaceWith(html);
const childNodes = (<DocumentFragment>(
XMLParser.parse(this[PropertySymbol.ownerDocument], html)
))[PropertySymbol.nodeArray];
this.replaceWith(...childNodes);
}

/**
Expand Down
32 changes: 12 additions & 20 deletions packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import XMLParser from '../../xml-parser/XMLParser.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import DocumentFragment from '../document-fragment/DocumentFragment.js';
import Document from '../document/Document.js';
Expand All @@ -19,17 +18,14 @@ export default class ParentNodeUtility {
* @param parentNode Parent node.
* @param nodes List of Node or DOMString.
*/
public static append(
parentNode: Element | Document | DocumentFragment,
...nodes: (Node | string)[]
): void {
public static append(parentNode: Element | Document | DocumentFragment, ...nodes: any[]): void {
for (const node of nodes) {
if (typeof node === 'string') {
XMLParser.parse(<Document>parentNode[PropertySymbol.ownerDocument], node, {
rootNode: parentNode
});
} else {
if (node instanceof Node) {
parentNode.appendChild(node);
} else {
parentNode.appendChild(
parentNode[PropertySymbol.ownerDocument].createTextNode(String(node))
);
}
}
}
Expand All @@ -46,17 +42,13 @@ export default class ParentNodeUtility {
): void {
const firstChild = parentNode.firstChild;
for (const node of nodes) {
if (typeof node === 'string') {
const childNodes = XMLParser.parse(
<Document>parentNode[PropertySymbol.ownerDocument],
node
)[PropertySymbol.nodeArray];

while (childNodes.length) {
parentNode.insertBefore(childNodes[0], firstChild);
}
} else {
if (node instanceof Node) {
parentNode.insertBefore(node, firstChild);
} else {
parentNode.insertBefore(
parentNode[PropertySymbol.ownerDocument].createTextNode(String(node)),
firstChild
);
}
}
}
Expand Down
33 changes: 15 additions & 18 deletions packages/happy-dom/test/nodes/child-node/ChildNodeUtility.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,25 @@ describe('ChildNodeUtility', () => {
it('Replaces a node with a mixed list of Node and DOMString (string).', () => {
const parent = document.createElement('div');
const newChildrenParent = document.createElement('div');
const newChildrenHtml =
'<span class="child4"></span><span class="child5"></span><span class="child6"></span>';
const newTextChildContent = '<span class="child4"></span>'; // this should not be parsed as HTML!
newChildrenParent.innerHTML =
'<span class="child7"></span><span class="child8"></span><span class="child9"></span>';
'<span class="child5"></span><span class="child6"></span><span class="child7"></span>';
parent.innerHTML =
'<span class="child1"></span><span class="child2"></span><span class="child3"></span>';

ChildNodeUtility.replaceWith(
parent.children[2],
...[newChildrenHtml, ...newChildrenParent.children]
...[newTextChildContent, ...newChildrenParent.children]
);
expect(parent.innerHTML).toBe(
'<span class="child1"></span><span class="child2"></span><span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span>'
'<span class="child1"></span><span class="child2"></span>&lt;span class="child4"&gt;&lt;/span&gt;<span class="child5"></span><span class="child6"></span><span class="child7"></span>'
);
expect(
Array.from(parent.children)
.map((element) => element.outerHTML)
.join('')
).toBe(
'<span class="child1"></span><span class="child2"></span><span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span>'
'<span class="child1"></span><span class="child2"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span>'
);
});
});
Expand Down Expand Up @@ -95,26 +94,25 @@ describe('ChildNodeUtility', () => {
it('Inserts a mixed list of Node and DOMString (string) before the child node.', () => {
const parent = document.createElement('div');
const newChildrenParent = document.createElement('div');
const newChildrenHtml =
'<span class="child4"></span><span class="child5"></span><span class="child6"></span>';
const newTextChildContent = '<span class="child4"></span>'; // this should not be parsed as HTML!
newChildrenParent.innerHTML =
'<span class="child7"></span><span class="child8"></span><span class="child9"></span>';
'<span class="child5"></span><span class="child6"></span><span class="child7"></span>';
parent.innerHTML =
'<span class="child1"></span><span class="child2"></span><span class="child3"></span>';

ChildNodeUtility.before(
parent.children[2],
...[newChildrenHtml, ...newChildrenParent.children]
...[newTextChildContent, ...newChildrenParent.children]
);
expect(parent.innerHTML).toBe(
'<span class="child1"></span><span class="child2"></span><span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span><span class="child3"></span>'
'<span class="child1"></span><span class="child2"></span>&lt;span class="child4"&gt;&lt;/span&gt;<span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child3"></span>'
);
expect(
Array.from(parent.children)
.map((element) => element.outerHTML)
.join('')
).toBe(
'<span class="child1"></span><span class="child2"></span><span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span><span class="child3"></span>'
'<span class="child1"></span><span class="child2"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child3"></span>'
);
});
});
Expand Down Expand Up @@ -163,26 +161,25 @@ describe('ChildNodeUtility', () => {
it('Inserts a mixed list of Node and DOMString (string) after the child node by appending the new nodes.', () => {
const parent = document.createElement('div');
const newChildrenParent = document.createElement('div');
const newChildrenHtml =
'<span class="child4"></span><span class="child5"></span><span class="child6"></span>';
const newTextChildContent = '<span class="child4"></span>'; // this should not be parsed as HTML!
newChildrenParent.innerHTML =
'<span class="child7"></span><span class="child8"></span><span class="child9"></span>';
'<span class="child5"></span><span class="child6"></span><span class="child7"></span>';
parent.innerHTML =
'<span class="child1"></span><span class="child2"></span><span class="child3"></span>';

ChildNodeUtility.after(
parent.children[2],
...[newChildrenHtml, ...newChildrenParent.children]
...[newTextChildContent, ...newChildrenParent.children]
);
expect(parent.innerHTML).toBe(
'<span class="child1"></span><span class="child2"></span><span class="child3"></span><span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span>'
'<span class="child1"></span><span class="child2"></span><span class="child3"></span>&lt;span class="child4"&gt;&lt;/span&gt;<span class="child5"></span><span class="child6"></span><span class="child7"></span>'
);
expect(
Array.from(parent.children)
.map((element) => element.outerHTML)
.join('')
).toBe(
'<span class="child1"></span><span class="child2"></span><span class="child3"></span><span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span>'
'<span class="child1"></span><span class="child2"></span><span class="child3"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span>'
);
});
});
Expand Down
9 changes: 4 additions & 5 deletions packages/happy-dom/test/nodes/element/Element.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1990,16 +1990,15 @@ describe('Element', () => {
it('Replaces a node with a mixed list of Node and DOMString (string).', () => {
const parent = document.createElement('div');
const newChildrenParent = document.createElement('div');
const newChildrenHtml =
'<span class="child4"></span><span class="child5"></span><span class="child6"></span>';
const newTextChildContent = '<span class="child4"></span>'; // this should not be parsed as HTML!
newChildrenParent.innerHTML =
'<span class="child7"></span><span class="child8"></span><span class="child9"></span>';
'<span class="child5"></span><span class="child6"></span><span class="child7"></span>';
parent.innerHTML =
'<span class="child1"></span><span class="child2"></span><span class="child3"></span>';

parent.children[2].replaceWith(...[newChildrenHtml, ...newChildrenParent.children]);
parent.children[2].replaceWith(...[newTextChildContent, ...newChildrenParent.children]);
expect(parent.innerHTML).toBe(
'<span class="child1"></span><span class="child2"></span><span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span>'
'<span class="child1"></span><span class="child2"></span>&lt;span class="child4"&gt;&lt;/span&gt;<span class="child5"></span><span class="child6"></span><span class="child7"></span>'
);
});
});
Expand Down
33 changes: 15 additions & 18 deletions packages/happy-dom/test/nodes/parent-node/ParentNodeUtility.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,22 @@ describe('ParentNodeUtility', () => {
it('Appends a mixed list of Node and DOMString after the last child of the ParentNode', () => {
const parent = document.createElement('div');
const newChildrenParent = document.createElement('div');
const newChildrenHtml =
'<span class="child4"></span><span class="child5"></span><span class="child6"></span>';
const newTextChildContent = '<span class="child4"></span>'; // this should not be parsed as HTML!
newChildrenParent.innerHTML =
'<span class="child7"></span><span class="child8"></span><span class="child9"></span>';
'<span class="child5"></span><span class="child6"></span><span class="child7"></span>';
parent.innerHTML =
'<span class="child1"></span><span class="child2"></span><span class="child3"></span>';

ParentNodeUtility.append(parent, ...[newChildrenHtml, ...newChildrenParent.children]);
ParentNodeUtility.append(parent, ...[newTextChildContent, ...newChildrenParent.children]);
expect(parent.innerHTML).toBe(
'<span class="child1"></span><span class="child2"></span><span class="child3"></span><span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span>'
'<span class="child1"></span><span class="child2"></span><span class="child3"></span>&lt;span class="child4"&gt;&lt;/span&gt;<span class="child5"></span><span class="child6"></span><span class="child7"></span>'
);
expect(
Array.from(parent.children)
.map((element) => element.outerHTML)
.join('')
).toBe(
'<span class="child1"></span><span class="child2"></span><span class="child3"></span><span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span>'
'<span class="child1"></span><span class="child2"></span><span class="child3"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span>'
);
});
});
Expand Down Expand Up @@ -83,23 +82,22 @@ describe('ParentNodeUtility', () => {
it('Prepends a mixed list of Node and DOMString before the first child of the ParentNode', () => {
const parent = document.createElement('div');
const newChildrenParent = document.createElement('div');
const newChildrenHtml =
'<span class="child4"></span><span class="child5"></span><span class="child6"></span>';
const newTextChildContent = '<span class="child4"></span>'; // this should not be parsed as HTML!
newChildrenParent.innerHTML =
'<span class="child7"></span><span class="child8"></span><span class="child9"></span>';
'<span class="child5"></span><span class="child6"></span><span class="child7"></span>';
parent.innerHTML =
'<span class="child1"></span><span class="child2"></span><span class="child3"></span>';

ParentNodeUtility.prepend(parent, ...[newChildrenHtml, ...newChildrenParent.children]);
ParentNodeUtility.prepend(parent, ...[newTextChildContent, ...newChildrenParent.children]);
expect(parent.innerHTML).toBe(
'<span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span><span class="child1"></span><span class="child2"></span><span class="child3"></span>'
'&lt;span class="child4"&gt;&lt;/span&gt;<span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child1"></span><span class="child2"></span><span class="child3"></span>'
);
expect(
Array.from(parent.children)
.map((element) => element.outerHTML)
.join('')
).toBe(
'<span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span><span class="child1"></span><span class="child2"></span><span class="child3"></span>'
'<span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child1"></span><span class="child2"></span><span class="child3"></span>'
);
});
});
Expand All @@ -108,26 +106,25 @@ describe('ParentNodeUtility', () => {
it('Replaces the existing children of a ParentNode with a mixed list of Node and DOMString.', () => {
const parent = document.createElement('div');
const newChildrenParent = document.createElement('div');
const newChildrenHtml =
'<span class="child4"></span><span class="child5"></span><span class="child6"></span>';
const newTextChildContent = '<span class="child4"></span>'; // this should not be parsed as HTML!
newChildrenParent.innerHTML =
'<span class="child7"></span><span class="child8"></span><span class="child9"></span>';
'<span class="child5"></span><span class="child6"></span><span class="child7"></span>';
parent.innerHTML =
'<span class="child1"></span><span class="child2"></span><span class="child3"></span>';

ParentNodeUtility.replaceChildren(
parent,
...[newChildrenHtml, ...newChildrenParent.children]
...[newTextChildContent, ...newChildrenParent.children]
);
expect(parent.innerHTML).toBe(
'<span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span>'
'&lt;span class="child4"&gt;&lt;/span&gt;<span class="child5"></span><span class="child6"></span><span class="child7"></span>'
);
expect(
Array.from(parent.children)
.map((element) => element.outerHTML)
.join('')
).toBe(
'<span class="child4"></span><span class="child5"></span><span class="child6"></span><span class="child7"></span><span class="child8"></span><span class="child9"></span>'
'<span class="child5"></span><span class="child6"></span><span class="child7"></span>'
);
});
});
Expand Down
12 changes: 6 additions & 6 deletions packages/happy-dom/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
"baseUrl": ".",
"composite": false,
"incremental": false,
"lib": [
"ES2022"
],
"lib": [
"ES2022"
],
"types": [
"node"
]
Expand All @@ -31,7 +31,7 @@
"@types/node",
"src"
],
"exclude": [
"@types/dom"
]
"exclude": [
"@types/dom"
]
}

0 comments on commit afd256b

Please sign in to comment.