Browse Source

Code coverage; Document 82.76%

• Moved all of preInsertionValidity to ParentNode; removed 
AbstractDocument
• Added host property to HTMLTemplateElement
• Increased required PHP version to 7.4
wrapper-classes
Dustin Wilson 3 years ago
parent
commit
081929c281
  1. 2
      composer.json
  2. 6
      lib/Attr.php
  3. 106
      lib/Document.php
  4. 42
      lib/DocumentFragment.php
  5. 14
      lib/Element.php
  6. 4
      lib/HTMLTemplateElement.php
  7. 20
      lib/traits/Node.php
  8. 128
      lib/traits/ParentNode.php
  9. 129
      tests/cases/TestDocument.php
  10. 44
      tests/cases/serializer/TestSerializer.php

2
composer.json

@ -3,7 +3,7 @@
"description": "Modern DOM library written in PHP for HTML documents",
"type": "library",
"require": {
"php": ">=7.1",
"php": ">=7.4",
"ext-dom": "*",
"mensbeam/html-parser": "dev-master"
},

6
lib/AbstractDocument.php → lib/Attr.php

@ -8,7 +8,7 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
// Exists so Document can extend methods from its traits.
abstract class AbstractDocument extends \DOMDocument {
use DocumentOrElement, MagicProperties, ParentNode, Walk;
class Attr extends \DOMAttr {
use Node;
}

106
lib/Document.php

@ -11,7 +11,9 @@ use MensBeam\HTML\Parser,
MensBeam\HTML\Parser\Data;
class Document extends AbstractDocument {
class Document extends \DOMDocument {
use DocumentOrElement, MagicProperties, ParentNode, Walk;
protected $_body = null;
/** Nonstandard */
protected $_documentEncoding = null;
@ -130,6 +132,7 @@ class Document extends AbstractDocument {
parent::__construct();
$this->registerNodeClass('DOMAttr', '\MensBeam\HTML\DOM\Attr');
$this->registerNodeClass('DOMDocument', '\MensBeam\HTML\DOM\Document');
$this->registerNodeClass('DOMComment', '\MensBeam\HTML\DOM\Comment');
$this->registerNodeClass('DOMDocumentFragment', '\MensBeam\HTML\DOM\DocumentFragment');
@ -176,32 +179,33 @@ class Document extends AbstractDocument {
}
}
public function createAttributeNS($namespaceURI, $qualifiedName): \DOMAttr {
public function createAttributeNS(?string $namespace, string $qualifiedName): \DOMAttr {
# The createAttributeNS(namespace, qualifiedName) method steps are:
# 1. Let namespace, prefix, and localName be the result of passing namespace and
# qualifiedName to validate and extract.
[ 'namespace' => $namespaceURI, 'prefix' => $prefix, 'localName' => $localName ] = $this->validateAndExtract($qualifiedName, $namespaceURI);
[ 'namespace' => $namespace, 'prefix' => $prefix, 'localName' => $localName ] = $this->validateAndExtract($qualifiedName, $namespace);
# 2. Return a new attribute whose namespace is namespace, namespace prefix is
# prefix, local name is localName, and node document is this.
// We need to do a couple more things here. PHP's XML-based DOM doesn't allow
// some characters. We have to coerce them sometimes.
try {
return parent::createAttributeNS($namespaceURI, $qualifiedName);
return parent::createAttributeNS($namespace, $qualifiedName);
} catch (\DOMException $e) {
// The element name is invalid for XML
// Replace any offending characters with "UHHHHHH" where H are the
// uppercase hexadecimal digits of the character's code point
if ($namespaceURI !== null) {
$qualifiedName = implode(":", array_map([$this, "coerceName"], explode(":", $qualifiedName, 2)));
if ($namespace !== null) {
$qualifiedName = implode(':', array_map([ $this, 'coerceName' ], explode(':', $qualifiedName, 2)));
} else {
$qualifiedName = $this->coerceName($qualifiedName);
}
return parent::createAttributeNS($namespaceURI, $qualifiedName);
return parent::createAttributeNS($namespace, $qualifiedName);
}
}
public function createElement($name, $value = null): Element {
public function createElement(string $name, ?string $value = null): Element {
# The createElement(localName, options) method steps are:
// DEVIATION: We cannot follow the createElement parameters per the DOM spec
// because we cannot change the parameters from \DOMDOcument. This is okay
@ -415,92 +419,6 @@ class Document extends AbstractDocument {
}
protected function preInsertionValidity(\DOMNode $node, ?\DOMNode $child = null) {
parent::preInsertionValidity($node, $child);
# 6. If parent is a document, and any of the statements below, switched on node,
# are true, then throw a "HierarchyRequestError" DOMException.
#
# DocumentFragment node
# If node has more than one element child or has a Text node child.
# Otherwise, if node has one element child and either parent has an element
# child, child is a doctype, or child is non-null and a doctype is following
# child.
if ($node instanceof \DOMDocumentType) {
if ($node->childNodes->length > 1 || $node->firstChild instanceof Text) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} else {
if ($node->firstChild instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
foreach ($this->childNodes as $c) {
if ($c instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
if ($child !== null) {
$n = $child;
while ($n = $n->nextSibling) {
if ($n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
}
}
# element
# parent has an element child, child is a doctype, or child is non-null and a
# doctype is following child.
elseif ($node instanceof Element) {
if ($child instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
if ($child !== null) {
$n = $child;
while ($n = $n->nextSibling) {
if ($n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
foreach ($this->childNodes as $c) {
if ($c instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
# doctype
# parent has a doctype child, child is non-null and an element is preceding
# child, or child is null and parent has an element child.
elseif ($node instanceof \DOMDocumentType) {
foreach ($this->childNodes as $c) {
if ($c instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
if ($child !== null) {
$n = $child;
while ($n = $n->prevSibling) {
if ($n instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
} else {
foreach ($this->childNodes as $c) {
if ($c instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
}
}
protected function serializeBlockElementFilter(\DOMNode $ignoredNode): \Closure {
$blockElementFilter = function($n) use ($ignoredNode) {
if (!$n->isSameNode($ignoredNode) && $n instanceof Element && $this->isHTMLNamespace($n) && (in_array($n->nodeName, self::BLOCK_ELEMENTS) || $n->walk(function($nn) {

42
lib/DocumentFragment.php

@ -8,8 +8,48 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM;
class DocumentFragment extends \DOMDocumentFragment {
use ParentNode, Walk;
use MagicProperties, ParentNode, Walk;
protected $_host = null;
protected function __get_host(): ?\DOMNode {
if ($this->_host === null) {
return $this->_host;
}
return $this->_host->get();
}
protected function __set_host(\DOMNode $value) {
if ($this->_host !== null) {
throw new Exception(Exception::READONLY_PROPERTY, 'host');
}
// Check to see if this is being set within the HTMLTemplateElement constructor
// and throw a read only exception otherwise. This will ensure the host remains
// readonly. YES. THIS IS DIRTY. We shouldn't do this, but there is no other
// option. While DocumentFragment could be created via a constructor it cannot
// be associated with a document unless created by
// Document::createDocumentFragment.
$backtrace = debug_backtrace();
$okay = false;
for ($len = count($backtrace), $i = $len - 1; $i >= 0; $i--) {
$cur = $backtrace[$i];
if ($cur['function'] === '__construct' && $cur['class'] === __NAMESPACE__ . '\\HTMLTemplateElement') {
$okay = true;
break;
}
}
if (!$okay) {
throw new Exception(Exception::READONLY_PROPERTY, 'host');
}
$this->_host = \WeakReference::create($value);
}
public function __toString() {
return $this->ownerDocument->saveHTML($this);

14
lib/Element.php

@ -17,16 +17,12 @@ class Element extends \DOMElement {
protected function __get_classList(): ?TokenList {
// MensBeam\HTML\DOM\TokenList uses WeakReference to prevent a circular reference,
// so it requires PHP 7.4 to work.
if (version_compare(\PHP_VERSION, '7.4.0', '>=')) {
// Only create the class list if it is actually used.
if ($this->_classList === null) {
$this->_classList = new TokenList($this, 'class');
}
return $this->_classList;
// Only create the class list if it is actually used.
if ($this->_classList === null) {
$this->_classList = new TokenList($this, 'class');
}
return null; // @codeCoverageIgnore
return $this->_classList;
}
protected function __get_innerHTML(): string {

4
lib/HTMLTemplateElement.php

@ -24,7 +24,9 @@ class HTMLTemplateElement extends Element {
$frag->removeChild($this);
unset($frag);
$this->content = $this->ownerDocument->createDocumentFragment();
$content = $this->ownerDocument->createDocumentFragment();
$content->host = $this;
$this->content = $content;
}

20
lib/traits/Node.php

@ -21,4 +21,24 @@ trait Node {
public function C14NFile($uri, $exclusive = null, $with_comments = null, ?array $xpath = null, ?array $ns_prefixes = null): bool {
return false;
}
public function getRootNode(): ?\DOMNode {
# The getRootNode(options) method steps are to return this’s shadow-including
# root if options["composed"] is true; otherwise this’s root.
// DEVIATION: This implementation does not have scripting, so there's no Shadow
// DOM. Therefore, there isn't a need for the options parameter.
# The root of an object is itself, if its parent is null, or else it is the root
# of its parent. The root of a tree is any object participating in that tree
# whose parent is null.
if ($this->parentNode === null) {
return $this;
}
return $this->moonwalk(function($n) {
if ($n->parentNode === null) {
return true;
}
})->current();
}
}

128
lib/traits/ParentNode.php

@ -120,41 +120,137 @@ trait ParentNode {
// "parent" in the spec comments below is $this
# 1. If parent is not a Document, DocumentFragment, or Element node, then throw
# a "HierarchyRequestError" DOMException.
# a "HierarchyRequestError" DOMException.
// Not necessary because they've been disabled and return hierarchy request
// errors in "leaf nodes".
// errors in ChildNode trait.
# 2. If node is a host-including inclusive ancestor of parent, then throw a
# "HierarchyRequestError" DOMException.
# "HierarchyRequestError" DOMException.
#
# An object A is a host-including inclusive ancestor of an object B, if either
# A is an inclusive ancestor of B, or if B’s root has a non-null host and A is a
# host-including inclusive ancestor of B’s root’s host.
// DEVIATION: The baseline for this library is PHP 7.1, and without
// WeakReferences we cannot add a host property to DocumentFragment to check
// against.
// This is handled just fine by PHP's DOM.
if ($node->parentNode !== null) {
if ($this->isSameNode($node) || $this->moonwalk(function($n) use($node) {
if ($n->isSameNode($node)) {
return true;
}
})->current() !== null) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} else {
$parentRoot = $this->getRootNode();
$parentRootHost = $parentRoot->host;
if ($parentRoot instanceof DocumentFragment && $parentRootHost !== null && ($host->isSameNode($node) || $host->moonwalk(function($n) use($node) {
if ($n->isSameNode($node)) {
return true;
}
})->current() !== null)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
# 3. If child is non-null and its parent is not parent, then throw a
# "NotFoundError" DOMException.
// This is handled just fine by PHP's DOM.
# "NotFoundError" DOMException.
if ($child !== null && ($child->parentNode === null || !$child->parentNode->isSameNode($this))) {
throw new DOMException(DOMException::NOT_FOUND);
}
# 4. If node is not a DocumentFragment, DocumentType, Element, Text,
# ProcessingInstruction, or Comment node, then throw a "HierarchyRequestError"
# DOMException.
# ProcessingInstruction, or Comment node, then throw a "HierarchyRequestError"
# DOMException.
if (!$node instanceof DocumentFragment && !$node instanceof \DOMDocumentType && !$node instanceof Element && !$node instanceof Text && !$node instanceof ProcessingInstruction && !$node instanceof Comment) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
# 5. If either node is a Text node and parent is a document, or node is a
# doctype and parent is not a document, then throw a "HierarchyRequestError"
# DOMException.
# doctype and parent is not a document, then throw a "HierarchyRequestError"
# DOMException.
// Not necessary because they've been disabled and return hierarchy request
// errors in "leaf nodes".
// errors in ChildNode trait
# 6. If parent is a document, and any of the statements below, switched on node,
# are true, then throw a "HierarchyRequestError" DOMException.
// Handled by the Document class.
# are true, then throw a "HierarchyRequestError" DOMException.
if ($this instanceof Document) {
# DocumentFragment node
# If node has more than one element child or has a Text node child.
# Otherwise, if node has one element child and either parent has an element
# child, child is a doctype, or child is non-null and a doctype is following
# child.
if ($node instanceof \DOMDocumentType) {
if ($node->childNodes->length > 1 || $node->firstChild instanceof Text) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
} else {
if ($node->firstChild instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
foreach ($this->childNodes as $c) {
if ($c instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
if ($child !== null) {
$n = $child;
while ($n = $n->nextSibling) {
if ($n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
}
}
# element
# parent has an element child, child is a doctype, or child is non-null and a
# doctype is following child.
elseif ($node instanceof Element) {
if ($child instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
if ($child !== null) {
$n = $child;
while ($n = $n->nextSibling) {
if ($n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
foreach ($this->childNodes as $c) {
if ($c instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
# doctype
# parent has a doctype child, child is non-null and an element is preceding
# child, or child is null and parent has an element child.
elseif ($node instanceof \DOMDocumentType) {
foreach ($this->childNodes as $c) {
if ($c instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
if ($child !== null) {
$n = $child;
while ($n = $n->prevSibling) {
if ($n instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
} else {
foreach ($this->childNodes as $c) {
if ($c instanceof Element) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
}
}
}
}
}

129
tests/cases/TestDocument.php

@ -43,11 +43,27 @@ class TestDocument extends \PHPUnit\Framework\TestCase {
}
/**
* @covers \MensBeam\HTML\DOM\Document::createAttribute
*/
public function testAttributeNodeCreationFailure(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::INVALID_CHARACTER);
$d = new Document();
$d->createAttribute('<ook>');
}
public function provideAttributeNodeNSCreation(): iterable {
return [
[ 'fake_ns', 'test', 'test', '' ],
[ 'fake_ns', 'test:test', 'test', 'test' ],
[ 'fake_ns', 'TEST:TEST', 'TEST', 'TEST' ]
[ 'fake_ns', 'test', 'fake_ns', '', 'test' ],
[ 'fake_ns', 'test:test', 'fake_ns', 'test', 'test' ],
[ 'fake_ns', 'TEST:TEST', 'fake_ns', 'TEST', 'TEST' ],
[ 'another_fake_ns', 'steaming💩:poop💩', 'another_fake_ns', 'steamingU01F4A9', 'poopU01F4A9' ],
// An empty string for a prefix is technically incorrect, but we cannot fix that.
[ '', 'poop💩', null, '', 'poopU01F4A9' ],
// An empty string for a prefix is technically incorrect, but we cannot fix that.
[ null, 'poop💩', null, '', 'poopU01F4A9' ]
];
}
@ -56,13 +72,13 @@ class TestDocument extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::validateAndExtract
*/
public function testAttributeNodeNSCreation(?string $nsIn, string $nameIn, string $local, string $prefix): void {
public function testAttributeNodeNSCreation(?string $nsIn, string $nameIn, ?string $nsExpected, ?string $prefixExpected, string $localNameExpected): void {
$d = new Document();
$d->appendChild($d->createElement('html'));
$a = $d->createAttributeNS($nsIn, $nameIn);
$this->assertSame($local, $a->localName);
$this->assertSame($nsIn, $a->namespaceURI);
$this->assertSame($prefix, $a->prefix);
$this->assertSame($nsExpected, $a->namespaceURI);
$this->assertSame($prefixExpected, $a->prefix);
$this->assertSame($localNameExpected, $a->localName);
}
@ -117,8 +133,9 @@ class TestDocument extends \PHPUnit\Framework\TestCase {
$d2 = new Document();
$d2->appendChild($d2->createElement('html'));
$d2->loadDOM($d);
$this->assertSame('MensBeam\HTML\DOM\Element', $d2->firstChild::class);
$this->assertSame('html', $d2->firstChild->nodeName);
$d3 = new Document($d);
$this->assertSame('MensBeam\HTML\DOM\Element', $d3->firstChild::class);
$this->assertSame('html', $d3->firstChild->nodeName);
// Test file source
$vfs = vfsStream::setup('DOM', 0777, [ 'test.html' => <<<HTML
@ -166,12 +183,14 @@ class TestDocument extends \PHPUnit\Framework\TestCase {
return [
// HTML element
[ 'div', 'div', Element::class ],
// HTML element and uppercase local name
// HTML element and uppercase qualified name
[ 'DIV', 'div', Element::class ],
// Template element
[ 'template', 'template', HTMLTemplateElement::class ],
// Template element and uppercase local name
[ 'TEMPLATE', 'template', HTMLTemplateElement::class ]
// Template element and uppercase qualified name
[ 'TEMPLATE', 'template', HTMLTemplateElement::class ],
// Name coercion
[ 'poop💩', 'poopU01F4A9', Element::class ]
];
}
@ -179,29 +198,56 @@ class TestDocument extends \PHPUnit\Framework\TestCase {
* @dataProvider provideElementCreation
* @covers \MensBeam\HTML\DOM\Document::createElement
*/
public function testElementCreation(string $localIn, string $localOut, string $class): void {
public function testElementCreation(string $nameIn, string $nameExpected, string $classExpected): void {
$d = new Document;
$n = $d->createElement($localIn);
$this->assertInstanceOf($class, $n);
$n = $d->createElement($nameIn);
$this->assertInstanceOf($classExpected, $n);
$this->assertNotNull($n->ownerDocument);
$this->assertSame($localOut, $n->localName);
$this->assertSame($nameExpected, $n->nodeName);
}
public function provideElementCreationFailures(): iterable {
return [
[ function() {
$d = new Document();
$d->createElement('ook', 'FAIL');
}, DOMException::NOT_SUPPORTED ],
[ function() {
$d = new Document();
$d->createElement('<ook>');
}, DOMException::INVALID_CHARACTER ]
];
}
/**
* @dataProvider provideElementCreationFailures
* @covers \MensBeam\HTML\DOM\Document::__construct
*/
public function testElementCreationFailures(\Closure $closure, int $errorCode): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode($errorCode);
$closure();
}
public function provideElementCreationNS(): iterable {
return [
// HTML element with a null namespace
[ null, null, 'div', 'div', Element::class ],
[ null, null, 'div', 'div', Element::class ],
// Template element with a null namespace
[ null, null, 'template', 'template', HTMLTemplateElement::class ],
[ null, null, 'template', 'template', HTMLTemplateElement::class ],
// Template element with a null namespace and uppercase name
[ null, null, 'TEMPLATE', 'TEMPLATE', HTMLTemplateElement::class ],
[ null, null, 'TEMPLATE', 'TEMPLATE', HTMLTemplateElement::class ],
// Template element
[ Parser::HTML_NAMESPACE, Parser::HTML_NAMESPACE, 'template', 'template', HTMLTemplateElement::class ],
[ Parser::HTML_NAMESPACE, Parser::HTML_NAMESPACE, 'template', 'template', HTMLTemplateElement::class ],
// SVG element with SVG namespace
[ Parser::SVG_NAMESPACE, Parser::SVG_NAMESPACE, 'svg', 'svg', Element::class ],
[ Parser::SVG_NAMESPACE, Parser::SVG_NAMESPACE, 'svg', 'svg', Element::class ],
// SVG element with SVG namespace and uppercase local name
[ Parser::SVG_NAMESPACE, Parser::SVG_NAMESPACE, 'SVG', 'SVG', Element::class ]
[ Parser::SVG_NAMESPACE, Parser::SVG_NAMESPACE, 'SVG', 'SVG', Element::class ],
// Name coercion
[ 'steaming💩', 'steaming💩', 'poop💩', 'poopU01F4A9', Element::class ]
];
}
@ -210,13 +256,42 @@ class TestDocument extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Document::createElementNS
* @covers \MensBeam\HTML\DOM\Document::validateAndExtract
*/
public function testElementCreationNS(?string $nsIn, ?string $nsOut, string $localIn, string $localOut, string $class): void {
public function testElementCreationNS(?string $nsIn, ?string $nsExpected, string $localNameIn, string $localNameExpected, string $classExpected): void {
$d = new Document();
$n = $d->createElementNS($nsIn, $localIn);
$this->assertInstanceOf($class, $n);
$n = $d->createElementNS($nsIn, $localNameIn);
$this->assertInstanceOf($classExpected, $n);
$this->assertNotNull($n->ownerDocument);
$this->assertSame($nsOut, $n->namespaceURI);
$this->assertSame($localOut, $n->localName);
$this->assertSame($nsExpected, $n->namespaceURI);
$this->assertSame($localNameExpected, $n->localName);
}
public function provideElementCreationNSFailures(): iterable {
return [
[ function() {
$d = new Document();
$d->createElementNS('ook', 'ook', 'FAIL');
}, DOMException::NOT_SUPPORTED ],
[ function() {
$d = new Document();
$d->createElementNS(null, '<ook>');
}, DOMException::INVALID_CHARACTER ],
[ function() {
$d = new Document();
$d->createElementNS(null, 'xmlns');
}, DOMException::NAMESPACE_ERROR ]
];
}
/**
* @dataProvider provideElementCreationNSFailures
* @covers \MensBeam\HTML\DOM\Document::createElementNS
* @covers \MensBeam\HTML\DOM\Document::validateAndExtract
*/
public function testElementCreationNSFailures(\Closure $closure, int $errorCode): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode($errorCode);
$closure();
}

44
tests/cases/serializer/TestSerializer.php

@ -8,7 +8,10 @@
declare(strict_types=1);
namespace MensBeam\HTML\DOM\TestCase;
use MensBeam\HTML\DOM\Document;
use MensBeam\HTML\DOM\{
Document,
DOMException
};
use MensBeam\HTML\Parser;
/**
@ -150,4 +153,43 @@ class TestSerializer extends \PHPUnit\Framework\TestCase {
}
return $out;
}
/** @covers \MensBeam\HTML\DOM\Document::saveHTML */
public function testSerializingDocumentType(): void {
$d = new Document();
$dt = $d->implementation->createDocumentType('ook', 'eek', 'ack');
$d->appendChild($dt);
$this->assertSame('<!DOCTYPE ook>', $d->saveHTML($dt));
}
/**
* @covers \MensBeam\HTML\DOM\Document::saveHTML
* @covers \MensBeam\HTML\DOM\Document::serializeFragment
* @covers \MensBeam\HTML\DOM\ToString::__toString
*/
public function testSerializingElements(): void {
$d = new Document();
$i = $d->createElement('input');
$i->appendChild($d->createTextNode('You should not see this text'));
$this->assertSame('<input>', (string)$i);
$this->assertSame('', $d->saveHTML($i));
$t = $d->createElement('template');
$t->content->appendChild($d->createTextNode('Ook!'));
$this->assertSame('<template>Ook!</template>', (string)$t);
$this->assertSame('Ook!', $d->saveHTML($t));
}
/** @covers \MensBeam\HTML\DOM\Document::saveHTML */
public function testSerializerFailure(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::WRONG_DOCUMENT);
$d = new Document();
$h = $d->createElement('html');
$d2 = new Document();
$d2->saveHTML($h);
}
}

Loading…
Cancel
Save