Browse Source

Started adding test cases for Node::replaceChild

wrapper-classes
Dustin Wilson 3 years ago
parent
commit
80135ef2a6
  1. 22
      lib/InnerNode/Document.php
  2. 38
      lib/Node.php
  3. 228
      tests/cases/TestNode.php

22
lib/InnerNode/Document.php

@ -49,19 +49,7 @@ class Document extends \DOMDocument {
}
public function getInnerNode(WrapperNode $node = null): ?\DOMNode {
if ($node === null) {
return null;
}
if ($node === $this) {
return $this;
}
if ($node instanceof \DOMDocument) {
throw new DOMException(DOMException::WRONG_DOCUMENT);
}
public function getInnerNode(WrapperNode $node): ?\DOMNode {
return $this->nodeMap->get($node);
}
@ -88,8 +76,6 @@ class Document extends \DOMDocument {
$className = 'CDATASection';
} elseif ($node instanceof \DOMComment) {
$className = 'Comment';
} elseif ($node instanceof \DOMDocument) {
$className = ($this->wrapperNode instanceof WrapperXMLDocument) ? 'XMLDocument' : 'Document';
} elseif ($node instanceof \DOMDocumentFragment) {
$className = 'DocumentFragment';
} elseif ($node instanceof \DOMDocumentType) {
@ -123,11 +109,7 @@ class Document extends \DOMDocument {
Reflection::setProtectedProperties($wrapperNode, [ '_ownerDocument' => $this->_wrapperNode ]);
}
// Don't put documents into the node map cache to prevent circular references.
if ($className !== 'Document') {
$this->nodeMap->set($wrapperNode, $node);
}
$this->nodeMap->set($wrapperNode, $node);
return $wrapperNode;
}
}

38
lib/Node.php

@ -560,6 +560,11 @@ abstract class Node {
}
public function replaceChild(Node $node, Node $child): Node {
$wrapperNode = $node;
$node = $this->getInnerNode($node);
$child = $this->getInnerNode($child);
$inner = $this->innerNode;
# The replaceChild(node, child) method steps are to return the result of
# replacing child with node within this.
// PHP's DOM has some issues due to not checking for some edge cases the DOM
@ -570,46 +575,49 @@ abstract class Node {
#
# 1. If parent is not a Document, DocumentFragment, or Element node, then throw
# a "HierarchyRequestError" DOMException.
if (!$this instanceof Document && !$this instanceof DocumentFragment && !$this instanceof Element) {
if (!$inner instanceof InnerDocument && !$inner instanceof \DOMDocumentFragment && !$inner instanceof \DOMElement) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
# 2. If node is a host-including inclusive ancestor of parent, then throw a
# "HierarchyRequestError" DOMException.
if ($node->contains($this)) {
// The specification makes no mention of checking to see if child is a
// host-including inclusive ancestor of parent or if child is a host-including
// inclusive ancestor of node, but it should. All browsers check for this.
if ($this->containsInner($node, $inner) || $this->containsInner($node, $child)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
# 3. If child’s parent is not parent, then throw a "NotFoundError" DOMException.
if ($child->parentNode !== $this) {
if ($child->parentNode !== $inner) {
throw new DOMException(DOMException::NOT_FOUND);
}
# 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData
# node, then throw a "HierarchyRequestError" DOMException.
if (!$node instanceof DocumentFragment && !$node instanceof DocumentType && !$node instanceof Element && !$node instanceof CharacterData) {
if (!$node instanceof \DOMDocumentFragment && !$node instanceof \DOMDocumentType && !$node instanceof \DOMElement && !$node instanceof \DOMCharacterData) {
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.
if (($node instanceof Text && $this instanceof Document) || ($node instanceof DocumentType && !$this instanceof Document)) {
if (($node instanceof \DOMText && $inner instanceof InnerDocument) || ($node instanceof \DOMDocumentType && !$inner instanceof InnerDocument)) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
}
# 6. If parent is a document, and any of the statements below, switched on the
# interface node implements, are true, then throw a "HierarchyRequestError".
if ($this instanceof Document) {
if ($inner instanceof InnerDocument) {
# ↪ DocumentFragment
# 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 that is not child or a doctype is following child.
if ($node instanceof DocumentFragment) {
if ($node instanceof \DOMDocumentFragment) {
$nodeChildElementCount = $node->childElementCount;
if ($nodeChildElementCount > 1) {
$n = $this->getInnerNode($node)->firstChild;
$n = $node->firstChild;
do {
if ($n instanceof \DOMText) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
@ -617,7 +625,7 @@ abstract class Node {
} while ($n = $n->nextSibling);
} elseif ($nodeChildElementCount === 1) {
$beforeChild = true;
$n = $this->getInnerNode($node)->firstChild;
$n = $node->firstChild;
do {
if (!$beforeChild && $n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
@ -635,9 +643,9 @@ abstract class Node {
# ↪ Element
# parent has an element child that is not child or a doctype is following
# child.
elseif ($node instanceof Element) {
elseif ($node instanceof \DOMElement) {
$beforeChild = true;
$n = $this->getInnerNode($node)->firstChild;
$n = $node->firstChild;
do {
if (!$beforeChild && $n instanceof \DOMDocumentType) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
@ -654,9 +662,9 @@ abstract class Node {
# ↪ DocumentType
# parent has a doctype child that is not child, or an element is preceding
# child.
elseif ($node instanceof DocumentType) {
elseif ($node instanceof \DOMDocumentType) {
$beforeChild = true;
$n = $this->getInnerNode($node)->firstChild;
$n = $node->firstChild;
do {
if (!$beforeChild && $n instanceof \DOMElement) {
throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR);
@ -672,8 +680,8 @@ abstract class Node {
}
// PHP's DOM does fine with the rest of the steps.
$this->innerNode->replaceChild($this->getInnerNode($node), $this->getInnerNode($child));
return $node;
$inner->replaceChild($node, $child);
return $wrapperNode;
}

228
tests/cases/TestNode.php

@ -10,6 +10,7 @@ namespace MensBeam\HTML\DOM\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
Node,
XMLDocument
};
@ -39,6 +40,7 @@ class TestNode extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createProcessingInstruction
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DocumentType::__construct
* @covers \MensBeam\HTML\DOM\DocumentType::__get_name
@ -57,6 +59,7 @@ class TestNode extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Node::cloneInnerNode
* @covers \MensBeam\HTML\DOM\Node::cloneWrapperNode
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::isEqualInnerNode
* @covers \MensBeam\HTML\DOM\Node::isEqualNode
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
@ -301,7 +304,36 @@ class TestNode extends \PHPUnit\Framework\TestCase {
$this->assertSame('<body><template></template>ook<div></div></body>', (string)$d->body);
}
/** @covers \MensBeam\HTML\DOM\Node::isEqualNode */
/**
* @covers \MensBeam\HTML\DOM\Node::isEqualNode
*
* @covers \MensBeam\HTML\DOM\Comment::__construct
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_implementation
* @covers \MensBeam\HTML\DOM\Document::createComment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DocumentType::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::createDocumentType
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::isEqualInnerNode
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\InnerNode\Document::__construct
* @covers \MensBeam\HTML\DOM\InnerNode\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::get
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::has
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::key
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::set
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::getProtectedProperty
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::setProtectedProperties
*/
public function testMethod_isEqualNode(): void {
$d = new Document();
@ -333,7 +365,6 @@ class TestNode extends \PHPUnit\Framework\TestCase {
}
/**
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
@ -371,12 +402,27 @@ class TestNode extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\Node::isDefaultNamespace
*
* @covers \MensBeam\HTML\DOM\Attr::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Attr::__set_value
* @covers \MensBeam\HTML\DOM\Comment::__construct
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::__get_implementation
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::createComment
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createElementNS
* @covers \MensBeam\HTML\DOM\Document::validateAndExtract
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DocumentType::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::createDocumentType
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::setAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNS
* @covers \MensBeam\HTML\DOM\Element::validateAndExtract
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
@ -392,6 +438,7 @@ class TestNode extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::set
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::getProtectedProperty
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::setProtectedProperties
*/
public function testMethod_isDefaultNamespace(): void {
$d = new Document();
@ -404,8 +451,8 @@ class TestNode extends \PHPUnit\Framework\TestCase {
$documentElement = $d->appendChild($d->createElement('html'));
$documentElement->setAttributeNS(Parser::XMLNS_NAMESPACE, 'xmlns:poop💩', 'https://poop💩.poop');
$body = $documentElement->appendChild($d->createElement('body'));
$svg = $body->appendChild($d->createElementNS(Parser::SVG_NAMESPACE, 'svg'));
$svg->setAttributeNS(Parser::XMLNS_NAMESPACE, 'xmlns:xlink', Parser::XLINK_NAMESPACE);
$mathml = $body->appendChild($d->createElementNS(Parser::MATHML_NAMESPACE, 'mathml'));
$mathml->setAttributeNS(Parser::XMLNS_NAMESPACE, 'xmlns:xlink', Parser::XLINK_NAMESPACE);
$comment = $d->createComment('Ook');
// Detached comment
@ -433,8 +480,8 @@ class TestNode extends \PHPUnit\Framework\TestCase {
$this->assertTrue($d->isDefaultNamespace(Parser::HTML_NAMESPACE));
// HTML namespace on element
$this->assertTrue($body->isDefaultNamespace(Parser::HTML_NAMESPACE));
// SVG namespace on svg element
$this->assertTrue($svg->isDefaultNamespace(Parser::SVG_NAMESPACE));
// MathML namespace on mathml element
$this->assertTrue($mathml->isDefaultNamespace(Parser::MATHML_NAMESPACE));
// On detached XML element with null namespace
$this->assertTrue($detached->isDefaultNamespace(null));
// Custom namespace on namespaced element
@ -452,7 +499,50 @@ class TestNode extends \PHPUnit\Framework\TestCase {
}
/** @covers \MensBeam\HTML\DOM\Node::lookupPrefix */
/**
* @covers \MensBeam\HTML\DOM\Node::lookupPrefix
*
* @covers \MensBeam\HTML\DOM\Attr::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement
* @covers \MensBeam\HTML\DOM\Attr::__set_value
* @covers \MensBeam\HTML\DOM\Comment::__construct
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::__get_implementation
* @covers \MensBeam\HTML\DOM\Document::createAttributeNS
* @covers \MensBeam\HTML\DOM\Document::createComment
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createElementNS
* @covers \MensBeam\HTML\DOM\Document::validateAndExtract
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DocumentType::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::createDocumentType
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::setAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttributeNodeNS
* @covers \MensBeam\HTML\DOM\Element::setAttributeNS
* @covers \MensBeam\HTML\DOM\Element::validateAndExtract
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::__get_parentElement
* @covers \MensBeam\HTML\DOM\Node::__get_parentNode
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::locateNamespacePrefix
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\InnerNode\Document::__construct
* @covers \MensBeam\HTML\DOM\InnerNode\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\InnerNode\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::get
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::has
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::key
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::set
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::getProtectedProperty
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::setProtectedProperties
*/
public function testMethod_lookupPrefix(): void {
$d = new Document();
$doctype = $d->appendChild($d->implementation->createDocumentType('html', '', ''));
@ -493,7 +583,28 @@ class TestNode extends \PHPUnit\Framework\TestCase {
}
/** @covers \MensBeam\HTML\DOM\Node::lookupNamespaceURI */
/**
* @covers \MensBeam\HTML\DOM\Node::lookupNamespaceURI
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::locateNamespace
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\InnerNode\Document::__construct
* @covers \MensBeam\HTML\DOM\InnerNode\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\InnerNode\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::get
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::has
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::key
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::set
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::getProtectedProperty
*/
public function testMethod_lookupNamespaceURI(): void {
$d = new Document();
$d->appendChild($d->createElement('html'));
@ -503,7 +614,33 @@ class TestNode extends \PHPUnit\Framework\TestCase {
}
/** @covers \MensBeam\HTML\DOM\Node::normalize */
/**
* @covers \MensBeam\HTML\DOM\Node::normalize
*
* @covers \MensBeam\HTML\DOM\Collection::__construct
* @covers \MensBeam\HTML\DOM\Collection::__get_length
* @covers \MensBeam\HTML\DOM\Collection::count
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_childNodes
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\InnerNode\Document::__construct
* @covers \MensBeam\HTML\DOM\InnerNode\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::get
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::has
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::key
* @covers \MensBeam\HTML\DOM\InnerNode\NodeMap::set
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\InnerNode\Reflection::getProtectedProperty
*/
public function testMethod_normalize(): void {
// Unless we implement Ranges PHP's DOM does this correctly.
$d = new Document();
@ -517,6 +654,77 @@ class TestNode extends \PHPUnit\Framework\TestCase {
}
/** @covers \MensBeam\HTML\DOM\Node::replaceChild */
public function testMethod_replaceChild(): void {
$d = new Document();
$d->appendChild($d->createElement('html'));
$d->documentElement->appendChild($d->createElement('body'));
$div = $d->body->appendChild($d->createElement('div'));
$ook = $d->body->replaceChild($d->createTextNode('ook'), $div);
$this->assertSame('<body>ook</body>', (string)$d->body);
$t = $d->body->replaceChild($d->createElement('template'), $ook);
$this->assertSame('<body><template></template></body>', (string)$d->body);
$d->body->replaceChild($d->createElement('br'), $t);
$this->assertSame('<body><br></body>', (string)$d->body);
}
public function provideMethod_replaceChild_errors(): iterable {
return [
[ function() {
$d = new Document();
$comment = $d->createComment('ook');
$comment->replaceChild($d->createTextNode('fail'), $d->createComment('ook'));
} ],
[ function() {
$d = new Document();
$documentElement = $d->appendChild($d->createElement('html'));
$body = $d->documentElement->appendChild($d->createElement('body'));
$body->replaceChild($documentElement, $d->createTextNode('ook'));
} ],
[ function() {
$d = new Document();
$documentElement = $d->appendChild($d->createElement('html'));
$body = $d->documentElement->appendChild($d->createElement('body'));
$documentElement->replaceChild($documentElement, $body);
} ],
[ function() {
$d = new Document();
$documentElement = $d->appendChild($d->createElement('html'));
$body = $d->documentElement->appendChild($d->createElement('body'));
$body->replaceChild($d->createTextNode('ook'), $documentElement);
}, DOMException::NOT_FOUND ],
[ function() {
$d = new Document();
$d2 = new Document();
$documentElement = $d->appendChild($d->createElement('html'));
$body = $d->documentElement->appendChild($d->createElement('body'));
$documentElement->replaceChild($d2, $body);
} ],
[ function() {
$d = new Document();
$documentElement = $d->appendChild($d->createElement('html'));
$d->replaceChild($d->createTextNode('ook'), $documentElement);
} ]
];
}
/**
* @dataProvider provideMethod_replaceChild_errors
* @covers \MensBeam\HTML\DOM\Node::replaceChild
*/
public function testMethod_replaceChild_errors(\Closure $closure, int $errorCode = DOMException::HIERARCHY_REQUEST_ERROR): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode($errorCode);
$closure();
}
/**
* @covers \MensBeam\HTML\DOM\Node::__get_childNodes
*

Loading…
Cancel
Save