diff --git a/src/dom/element.ts b/src/dom/element.ts
index 8a14950..1edf082 100644
--- a/src/dom/element.ts
+++ b/src/dom/element.ts
@@ -591,7 +591,41 @@ export class Element extends Node {
}
set outerHTML(html: string) {
- // TODO: Someday...
+ if (this.parentNode) {
+ const { parentElement, parentNode } = this;
+ let contextLocalName = parentElement?.localName;
+
+ switch (parentNode.nodeType) {
+ case NodeType.DOCUMENT_NODE: {
+ throw new DOMException(
+ "Modifications are not allowed for this document",
+ );
+ }
+
+ // setting outerHTML, step 4. Document Fragment
+ // ref: https://w3c.github.io/DOM-Parsing/#dom-element-outerhtml
+ case NodeType.DOCUMENT_FRAGMENT_NODE: {
+ contextLocalName = "body";
+ // fall-through
+ }
+
+ default: {
+ const { childNodes: newChildNodes } =
+ fragmentNodesFromString(html, contextLocalName!).childNodes[0];
+ const mutator = parentNode._getChildNodesMutator();
+ const insertionIndex = mutator.indexOf(this);
+
+ for (let i = newChildNodes.length - 1; i >= 0; i--) {
+ const child = newChildNodes[i];
+ mutator.splice(insertionIndex, 0, child);
+ child._setParent(parentNode);
+ child._setOwnerDocument(parentNode.ownerDocument);
+ }
+
+ this.remove();
+ }
+ }
+ }
}
get innerHTML(): string {
diff --git a/test/units/Element-outerHTML.ts b/test/units/Element-outerHTML.ts
index 543e699..428c9fc 100644
--- a/test/units/Element-outerHTML.ts
+++ b/test/units/Element-outerHTML.ts
@@ -1,5 +1,8 @@
-import { DOMParser } from "../../deno-dom-wasm.ts";
-import { assertStrictEquals as assertEquals } from "https://deno.land/std@0.85.0/testing/asserts.ts";
+import { DocumentFragment, DOMParser } from "../../deno-dom-wasm.ts";
+import {
+ assertStrictEquals as assertEquals,
+ assertThrows,
+} from "https://deno.land/std@0.85.0/testing/asserts.ts";
// TODO: More comprehensive tests
@@ -69,3 +72,107 @@ Deno.test("Element.outerHTML won't overflow the stack for deeply nested HTML", (
const htmlElement = doc.documentElement!;
assertEquals(htmlElement.outerHTML.length > 0, true);
});
+
+Deno.test("Element.outerHTML can be set to replace element", () => {
+ const doc = new DOMParser().parseFromString(
+ `
+
+
+
+ `,
+ "text/html",
+ )!;
+ const parent = doc.querySelector(".parent")!;
+ const child = doc.querySelector(".child")!;
+ const otherParent = doc.querySelector(".otherparent")!;
+ const otherChild = doc.querySelector(".otherchild")!;
+ const tbody = doc.querySelector("tbody")!;
+ const tr = tbody.children[0];
+
+ const newHTML =
+ `foo fibtext nodesbar`;
+ const serializedNewHTML = newHTML.replace("qux", '"qux"');
+ child.outerHTML = newHTML;
+
+ assertEquals(child.parentNode, null);
+ assertEquals(
+ Array.from(parent.childNodes).find((node) => node === child),
+ undefined,
+ );
+ assertEquals(parent.children.length, 2);
+ assertEquals(parent.childNodes.length, 4);
+ assertEquals(parent.innerHTML, serializedNewHTML);
+
+ const newChild = parent.children[0];
+ newChild.outerHTML = `goodbye |
`;
+ assertEquals(newChild.parentNode, null);
+ assertEquals(
+ Array.from(parent.childNodes).find((node) => node === newChild),
+ undefined,
+ );
+ assertEquals(parent.children.length, 1);
+ assertEquals(parent.childNodes.length, 4);
+ assertEquals(
+ parent.innerHTML,
+ "goodbye" + serializedNewHTML.slice(serializedNewHTML.indexOf("text")),
+ );
+
+ assertEquals(tbody.innerHTML, `hello |
`);
+ tr.outerHTML = `goodbye |
`;
+
+ assertEquals(tr.parentNode, null);
+ assertEquals(
+ Array.from(tbody.childNodes).find((node) => node === tr),
+ undefined,
+ );
+ assertEquals(tbody.children.length, 1);
+ assertEquals(tbody.childNodes.length, 1);
+ assertEquals(tbody.innerHTML, `goodbye |
`);
+
+ otherChild.outerHTML = ``;
+
+ assertEquals(otherChild.parentNode, null);
+ assertEquals(otherChild.parentElement, null);
+ assertEquals(
+ Array.from(otherParent.childNodes).find((node) => node === otherChild),
+ undefined,
+ );
+ assertEquals(otherParent.children.length, 2);
+ assertEquals(otherParent.childNodes.length, 4);
+ assertEquals(
+ otherParent.outerHTML,
+ ``,
+ );
+
+ const solitaryDiv = doc.createElement("div");
+ solitaryDiv.outerHTML = `no-op`;
+
+ assertEquals(solitaryDiv.parentNode, null);
+ assertEquals(solitaryDiv.outerHTML, ``);
+
+ const frag = doc.createDocumentFragment();
+ const fragChild = doc.createElement("div");
+ frag.appendChild(fragChild);
+ assertEquals(fragChild.parentNode, frag);
+ assertEquals(frag.childNodes[0].nodeName, "DIV");
+ assertEquals(frag.childNodes.length, 1);
+
+ fragChild.outerHTML = `
+
+
+ only text nodes allowed |
+
+ `.replace(/\s{2,}/g, "");
+
+ assertEquals(fragChild.parentNode, null);
+ assertEquals(frag.children.length, 2);
+ assertEquals(frag.childNodes.length, 4);
+ assertEquals(
+ Array.from(frag.childNodes).map((node) => node.nodeName).join("-"),
+ "ASIDE-#comment-#text-BUTTON",
+ );
+
+ assertThrows(() => {
+ doc.documentElement!.outerHTML = "not new document element
";
+ });
+});