From 80135ef2a6066c312cd2b614a97c624abf7f84fb Mon Sep 17 00:00:00 2001 From: Dustin Wilson Date: Mon, 1 Nov 2021 23:38:56 -0500 Subject: [PATCH] Started adding test cases for Node::replaceChild --- lib/InnerNode/Document.php | 22 +--- lib/Node.php | 38 ++++--- tests/cases/TestNode.php | 228 +++++++++++++++++++++++++++++++++++-- 3 files changed, 243 insertions(+), 45 deletions(-) diff --git a/lib/InnerNode/Document.php b/lib/InnerNode/Document.php index 3b38de9..193e8fa 100644 --- a/lib/InnerNode/Document.php +++ b/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; } } diff --git a/lib/Node.php b/lib/Node.php index 6b90606..10b1a12 100644 --- a/lib/Node.php +++ b/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; } diff --git a/tests/cases/TestNode.php b/tests/cases/TestNode.php index 7d9651b..3a8103b 100644 --- a/tests/cases/TestNode.php +++ b/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('ook
', (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('ook', (string)$d->body); + + $t = $d->body->replaceChild($d->createElement('template'), $ook); + + $this->assertSame('', (string)$d->body); + + $d->body->replaceChild($d->createElement('br'), $t); + + $this->assertSame('
', (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 *