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 8911a01da..4b99ed0a5 100644 --- a/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts +++ b/packages/happy-dom/src/nodes/html-template-element/HTMLTemplateElement.ts @@ -1,11 +1,9 @@ -import Node from '../node/Node'; import HTMLElement from '../html-element/HTMLElement'; import IDocumentFragment from '../document-fragment/IDocumentFragment'; import INode from '../node/INode'; import IHTMLTemplateElement from './IHTMLTemplateElement'; import XMLParser from '../../xml-parser/XMLParser'; import XMLSerializer from '../../xml-serializer/XMLSerializer'; -import DOMException from '../../exception/DOMException'; /** * HTML Template Element. @@ -36,34 +34,18 @@ export default class HTMLTemplateElement extends HTMLElement implements IHTMLTem } } - /** - * @override - */ - public get outerHTML(): string { - return new XMLSerializer().serializeToString(this.content); - } - - /** - * @override - */ - public set outerHTML(_html: string) { - throw new DOMException( - `Failed to set the 'outerHTML' property on 'Element': This element has no parent node.` - ); - } - /** * @override */ public get previousSibling(): INode { - return this.content.previousSibling; + return null; } /** * @override */ public get nextSibling(): INode { - return this.content.nextSibling; + return null; } /** @@ -102,7 +84,7 @@ export default class HTMLTemplateElement extends HTMLElement implements IHTMLTem /** * @override */ - public removeChild(node: Node): INode { + public removeChild(node: INode): INode { return this.content.removeChild(node); } diff --git a/packages/happy-dom/src/xml-serializer/XMLSerializer.ts b/packages/happy-dom/src/xml-serializer/XMLSerializer.ts index 00814fcd7..9a255bb57 100644 --- a/packages/happy-dom/src/xml-serializer/XMLSerializer.ts +++ b/packages/happy-dom/src/xml-serializer/XMLSerializer.ts @@ -5,6 +5,7 @@ import DocumentType from '../nodes/document-type/DocumentType'; import { escape } from 'he'; import INode from '../nodes/node/INode'; import IElement from '../nodes/element/IElement'; +import IHTMLTemplateElement from '../nodes/html-template-element/IHTMLTemplateElement'; /** * Utility for converting an element to string. @@ -28,9 +29,13 @@ export default class XMLSerializer { return `<${tagName}${this._getAttributes(element)}>`; } + const childNodes = + element.tagName === 'TEMPLATE' + ? (root).content.childNodes + : root.childNodes; let innerHTML = ''; - for (const node of root.childNodes) { + for (const node of childNodes) { innerHTML += this.serializeToString(node, options); } diff --git a/packages/happy-dom/test/nodes/html-template-element/HTMLTemplateElement.test.ts b/packages/happy-dom/test/nodes/html-template-element/HTMLTemplateElement.test.ts index 22d3879c3..4fcd54d7c 100644 --- a/packages/happy-dom/test/nodes/html-template-element/HTMLTemplateElement.test.ts +++ b/packages/happy-dom/test/nodes/html-template-element/HTMLTemplateElement.test.ts @@ -1,6 +1,7 @@ import Window from '../../../src/window/Window'; import Document from '../../../src/nodes/document/Document'; import HTMLTemplateElement from '../../../src/nodes/html-template-element/HTMLTemplateElement'; +import XMLSerializer from '../../../src/xml-serializer/XMLSerializer'; describe('HTMLTemplateElement', () => { let window: Window; @@ -17,13 +18,215 @@ describe('HTMLTemplateElement', () => { jest.restoreAllMocks(); }); - it('InnerHTML', () => { - const div = '
happy-dom is cool!
'; - expect(element.content.childNodes.length).toBe(0); - element.innerHTML = div; - expect(element.innerHTML).toBe(div); - expect(element.content.childNodes.length).toBe(1); - element.innerHTML = ''; - expect(element.content.childNodes.length).toBe(0); + describe('get innerHTML()', () => { + it('Returns inner HTML of the "content" node.', () => { + const div = document.createElement('div'); + + div.innerHTML = 'Test'; + + expect(element.content.childNodes.length).toBe(0); + expect(element.innerHTML).toBe(''); + + element.appendChild(div); + + expect(element.childNodes.length).toBe(0); + expect(element.innerHTML).toBe('
Test
'); + expect(new XMLSerializer().serializeToString(element.content)).toBe('
Test
'); + + element.removeChild(div); + + expect(element.content.childNodes.length).toBe(0); + expect(element.innerHTML).toBe(''); + }); + }); + + describe('set innerHTML()', () => { + it('Serializes the HTML into nodes and appends them to the "content" node.', () => { + expect(element.content.childNodes.length).toBe(0); + expect(element.innerHTML).toBe(''); + + element.innerHTML = '
Test
'; + + expect(element.childNodes.length).toBe(0); + expect(element.innerHTML).toBe('
Test
'); + expect(new XMLSerializer().serializeToString(element.content)).toBe('
Test
'); + + element.innerHTML = ''; + + expect(element.content.childNodes.length).toBe(0); + expect(element.innerHTML).toBe(''); + }); + }); + + describe('get outerHTML()', () => { + it('Serializes the HTML into nodes and appends them to the "content" node.', () => { + expect(element.content.childNodes.length).toBe(0); + expect(element.innerHTML).toBe(''); + + element.innerHTML = '
Test
'; + + expect(element.childNodes.length).toBe(0); + expect(element.outerHTML).toBe(''); + + element.innerHTML = ''; + + expect(element.outerHTML).toBe(''); + }); + }); + + describe('set outerHTML()', () => { + it('Replaces the template with a span.', () => { + element.innerHTML = '
Test
'; + + document.body.appendChild(element); + + expect(document.body.innerHTML).toBe(''); + + element.outerHTML = 'Test'; + + expect(document.body.innerHTML).toBe('Test'); + }); + }); + + describe('get previousSibling()', () => { + it('Returns null.', () => { + element.innerHTML = '
Test
'; + expect(element.previousSibling).toBe(null); + }); + }); + + describe('get nextSibling()', () => { + it('Returns null.', () => { + element.innerHTML = '
Test
'; + expect(element.nextSibling).toBe(null); + }); + }); + + describe('get firstChild()', () => { + it('Returns first child.', () => { + const div = document.createElement('div'); + const span = document.createElement('span'); + element.appendChild(div); + element.appendChild(span); + expect(element.firstChild).toBe(div); + }); + }); + + describe('get lastChild()', () => { + it('Returns last child.', () => { + const div = document.createElement('div'); + const span = document.createElement('span'); + element.appendChild(div); + element.appendChild(span); + expect(element.lastChild).toBe(span); + }); + }); + + describe('getInnerHTML()', () => { + it('Returns inner HTML of the "content" node.', () => { + const div = document.createElement('div'); + + div.innerHTML = 'Test'; + + expect(element.content.childNodes.length).toBe(0); + expect(element.getInnerHTML()).toBe(''); + + element.appendChild(div); + + expect(element.childNodes.length).toBe(0); + expect(element.getInnerHTML()).toBe('
Test
'); + expect(new XMLSerializer().serializeToString(element.content)).toBe('
Test
'); + + element.removeChild(div); + + expect(element.content.childNodes.length).toBe(0); + expect(element.getInnerHTML()).toBe(''); + }); + + it('Returns HTML of children and shadow roots of custom elements as a concatenated string.', () => { + const div = document.createElement('div'); + + element.appendChild(div); + + jest + .spyOn(XMLSerializer.prototype, 'serializeToString') + .mockImplementation((rootElement, options) => { + expect(rootElement).toBe(div); + expect(options).toEqual({ includeShadowRoots: true }); + return 'EXPECTED_HTML'; + }); + + expect(element.getInnerHTML({ includeShadowRoots: true })).toBe('EXPECTED_HTML'); + }); + }); + + describe('appendChild()', () => { + it('Appends a node to the "content" node.', () => { + const div = document.createElement('div'); + + expect(element.childNodes.length).toBe(0); + expect(element.content.childNodes.length).toBe(0); + + element.appendChild(div); + + expect(element.childNodes.length).toBe(0); + expect(element.content.childNodes.length).toBe(1); + expect(element.content.childNodes[0] === div).toBe(true); + + element.removeChild(div); + + expect(element.childNodes.length).toBe(0); + expect(element.content.childNodes.length).toBe(0); + }); + }); + + describe('removeChild()', () => { + it('Removes a node from the "content" node.', () => { + const div = document.createElement('div'); + + element.appendChild(div); + + expect(element.childNodes.length).toBe(0); + expect(element.content.childNodes.length).toBe(1); + + element.removeChild(div); + + expect(element.childNodes.length).toBe(0); + expect(element.content.childNodes.length).toBe(0); + }); + }); + + describe('insertBefore()', () => { + it('Inserts a node before another node in the "content" node.', () => { + const div = document.createElement('div'); + const span = document.createElement('span'); + const underline = document.createElement('u'); + element.appendChild(div); + element.appendChild(span); + element.insertBefore(underline, span); + expect(element.innerHTML).toBe('
'); + }); + }); + + describe('replaceChild()', () => { + it('Removes a node from the "content" node.', () => { + const div = document.createElement('div'); + const span = document.createElement('span'); + const underline = document.createElement('u'); + const bold = document.createElement('b'); + element.appendChild(div); + element.appendChild(underline); + element.appendChild(span); + element.replaceChild(bold, underline); + expect(element.innerHTML).toBe('
'); + }); + }); + + describe('cloneNode()', () => { + it('Clones the nodes of the "content" node.', () => { + element.innerHTML = '
'; + const clone = element.cloneNode(true); + expect(clone.innerHTML).toBe('
'); + }); }); }); diff --git a/packages/happy-dom/test/xml-parser/XMLParser.test.ts b/packages/happy-dom/test/xml-parser/XMLParser.test.ts index 1909ffef9..c2ef811a8 100644 --- a/packages/happy-dom/test/xml-parser/XMLParser.test.ts +++ b/packages/happy-dom/test/xml-parser/XMLParser.test.ts @@ -167,7 +167,7 @@ describe('XMLParser', () => { const root = XMLParser.parse( window.document, `
- + @@ -175,7 +175,7 @@ describe('XMLParser', () => { ); expect((root.children[0].children[0]).innerText).toBe( - `if(11){console.log("1")}` + `if(11){console.log("1")}` ); expect((root.children[0].children[1]).innerText).toBe(''); @@ -186,10 +186,10 @@ describe('XMLParser', () => { expect(new XMLSerializer().serializeToString(root).replace(/[\s]/gm, '')).toBe( `
- + - +
`.replace(/[\s]/gm, '') ); diff --git a/packages/happy-dom/test/xml-serializer/XMLSerializer.test.ts b/packages/happy-dom/test/xml-serializer/XMLSerializer.test.ts index d50c15511..fe12593b8 100644 --- a/packages/happy-dom/test/xml-serializer/XMLSerializer.test.ts +++ b/packages/happy-dom/test/xml-serializer/XMLSerializer.test.ts @@ -74,6 +74,14 @@ describe('XMLSerializer', () => { expect(xmlSerializer.serializeToString(div)).toBe('
Text 1.Text 2.
'); }); + it('Serializes a template node.', () => { + const template = document.createElement('template'); + template.innerHTML = '
Test
'; + expect(xmlSerializer.serializeToString(template)).toBe( + '' + ); + }); + it('Serializes a mix of nodes.', () => { const div = document.createElement('div'); const comment1 = document.createComment('Comment 1.');