From 302ca7839c52938c89d985f8ad62ec10e82fb586 Mon Sep 17 00:00:00 2001 From: Dustin Wilson Date: Fri, 8 Oct 2021 16:55:05 -0500 Subject: [PATCH] More tests --- lib/Comment.php | 2 +- lib/Document.php | 11 ++++++- lib/Element.php | 2 +- lib/ElementMap.php | 23 +++++---------- lib/ProcessingInstruction.php | 2 +- lib/Text.php | 2 +- lib/traits/ChildNode.php | 28 ++++-------------- lib/traits/DocumentOrElement.php | 7 +++-- lib/traits/LeafNode.php | 33 +++++++++++++++++++++ lib/traits/MagicProperties.php | 6 ---- lib/traits/Node.php | 2 +- lib/traits/ParentNode.php | 2 +- tests/cases/TestChildNode.php | 34 ++++++++++++++++++++++ tests/cases/TestDocument.php | 12 ++++++++ tests/cases/TestElementMap.php | 39 +++++++++++++++++++++++++ tests/cases/TestException.php | 5 +--- tests/cases/TestLeafNode.php | 49 ++++++++++++++++++++++++++++++++ tests/phpunit.dist.xml | 3 ++ 18 files changed, 204 insertions(+), 58 deletions(-) create mode 100644 lib/traits/LeafNode.php create mode 100644 tests/cases/TestChildNode.php create mode 100644 tests/cases/TestElementMap.php create mode 100644 tests/cases/TestLeafNode.php diff --git a/lib/Comment.php b/lib/Comment.php index cd87ded..dce9487 100644 --- a/lib/Comment.php +++ b/lib/Comment.php @@ -9,5 +9,5 @@ declare(strict_types=1); namespace MensBeam\HTML\DOM; class Comment extends \DOMComment { - use ChildNode, Moonwalk, ToString; + use ChildNode, LeafNode, Moonwalk, ToString; } diff --git a/lib/Document.php b/lib/Document.php index 818d8fa..3cd7649 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -205,6 +205,10 @@ class Document extends \DOMDocument { } } + public function createCDATASection(string $data) { + throw new DOMException(DOMException::NOT_SUPPORTED, __CLASS__ . ' is only meant for HTML; CDATA sections do not exist in HTML DOM'); + } + 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 @@ -300,11 +304,16 @@ class Document extends \DOMDocument { } } - public function createEntityReference(string $name): bool { + public function createEntityReference(string $name) { throw new DOMException(DOMException::NOT_SUPPORTED, __CLASS__ . ' is only meant for HTML; entity references do not exist in HTML DOM'); } public function importNode(\DOMNode $node, bool $deep = false) { + // Disable importing of PHP's XML DOM-specific nodes. + if ($node instanceof \DOMCDATASection || $node instanceof \DOMEntity || $node instanceof \DOMEntityReference) { + throw new DOMException(DOMException::NOT_SUPPORTED, 'Clever little fucker, aren\'t you?'); + } + $node = parent::importNode($node, $deep); if ($node instanceof \DOMElement || $node instanceof \DOMDocumentFragment) { diff --git a/lib/Element.php b/lib/Element.php index 12ecd1d..05fdc4d 100644 --- a/lib/Element.php +++ b/lib/Element.php @@ -11,7 +11,7 @@ use MensBeam\HTML\Parser; class Element extends \DOMElement { - use DocumentOrElement, MagicProperties, Moonwalk, ParentNode, ToString, Walk; + use ChildNode, DocumentOrElement, MagicProperties, Moonwalk, ParentNode, ToString, Walk; protected ?TokenList $_classList = null; diff --git a/lib/ElementMap.php b/lib/ElementMap.php index cb51a7e..ca66b00 100644 --- a/lib/ElementMap.php +++ b/lib/ElementMap.php @@ -29,18 +29,16 @@ class ElementMap { self::$documents[] = $document; self::$elements[count(self::$documents) - 1][] = $element; return true; - } else { - foreach (self::$elements[$index] as $v) { - if ($v->isSameNode($element)) { - return false; - } - } + } - self::$elements[$index][] = $element; - return true; + foreach (self::$elements[$index] as $v) { + if ($v->isSameNode($element)) { + return false; + } } - return false; + self::$elements[$index][] = $element; + return true; } public static function delete(Element $element): bool { @@ -72,13 +70,6 @@ class ElementMap { return false; } - public static function getIterator(Document $document): \Traversable { - $index = self::index($document); - foreach (self::$elements[$index] as $v) { - yield $v; - } - } - public static function has(Element $element): bool { $document = $element->ownerDocument; $index = self::index($document); diff --git a/lib/ProcessingInstruction.php b/lib/ProcessingInstruction.php index 9e6a3cb..74f2e2a 100644 --- a/lib/ProcessingInstruction.php +++ b/lib/ProcessingInstruction.php @@ -9,5 +9,5 @@ declare(strict_types=1); namespace MensBeam\HTML\DOM; class ProcessingInstruction extends \DOMProcessingInstruction { - use ChildNode, Moonwalk, ToString; + use ChildNode, LeafNode, Moonwalk, ToString; } diff --git a/lib/Text.php b/lib/Text.php index 9fd4768..ed0fbe1 100644 --- a/lib/Text.php +++ b/lib/Text.php @@ -9,5 +9,5 @@ declare(strict_types=1); namespace MensBeam\HTML\DOM; class Text extends \DOMText { - use ChildNode, Moonwalk, ToString; + use ChildNode, LeafNode, Moonwalk, ToString; } diff --git a/lib/traits/ChildNode.php b/lib/traits/ChildNode.php index 39d25ee..baadd31 100644 --- a/lib/traits/ChildNode.php +++ b/lib/traits/ChildNode.php @@ -10,8 +10,6 @@ namespace MensBeam\HTML\DOM; trait ChildNode { - use Node; - public function after(...$nodes): void { # The after(nodes) method steps are: # @@ -25,15 +23,15 @@ trait ChildNode { # 3. Let viableNextSibling be this’s first following sibling not in nodes; # otherwise null. $n = $this; - $nextViableSibling = null; - while ($n = $n->followingSibling) { + $viableNextSibling = null; + while ($n = $n->nextSibling) { foreach ($nodes as $nodeOrString) { - if ($nodeOrString instanceof \DOMNode && $nodeOrString->isSameNode($n->followingSibling)) { - continue; + if ($nodeOrString instanceof \DOMNode && $nodeOrString->isSameNode($n)) { + continue 2; } } - $nextViableSibling = $n; + $viableNextSibling = $n; break; } @@ -44,20 +42,4 @@ trait ChildNode { # 5. Pre-insert node into parent before viableNextSibling. $parent->insertBefore($node, $viableNextSibling); } - - public function appendChild($node) { - throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); - } - - public function insertBefore($node, $child = null) { - throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); - } - - public function removeChild($child) { - throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); - } - - public function replaceChild($node, $child) { - throw new DOMException(DOMException::HIERARCHY_REQUEST_ERROR); - } } diff --git a/lib/traits/DocumentOrElement.php b/lib/traits/DocumentOrElement.php index 4d5ccf0..547b8d8 100644 --- a/lib/traits/DocumentOrElement.php +++ b/lib/traits/DocumentOrElement.php @@ -11,8 +11,11 @@ use MensBeam\HTML\Parser, MensBeam\HTML\Parser\NameCoercion; -// This exists because the DOM spec for some stupid reason doesn't give -// DocumentFragment some methods. +/** + * Not in standard. Exists so Document and Element can share some properties and + * methods. For instance, getElementsByClassName is mentioned in the standard in + * both the Document and Element interfaces. + */ trait DocumentOrElement { use NameCoercion; diff --git a/lib/traits/LeafNode.php b/lib/traits/LeafNode.php new file mode 100644 index 0000000..50c4be2 --- /dev/null +++ b/lib/traits/LeafNode.php @@ -0,0 +1,33 @@ +getMagicPropertyMethodName($name); if ($methodName === null) { throw new Exception(Exception::NONEXISTENT_PROPERTY, $name); @@ -32,16 +30,12 @@ trait MagicProperties { } public function __set(string $name, $value) { - // If a setter method exists return that. $methodName = $this->getMagicPropertyMethodName($name, false); if ($methodName !== null) { call_user_func([ $this, $methodName ], $value); return; } - // Otherwise, if a getter exists then trigger a readonly property fatal error. - // Finally, if a getter doesn't exist trigger a property does not exist fatal - // error. if ($this->getMagicPropertyMethodName($name) !== null) { throw new Exception(Exception::READONLY_PROPERTY, $name); } else { diff --git a/lib/traits/Node.php b/lib/traits/Node.php index a30b7e4..7548579 100644 --- a/lib/traits/Node.php +++ b/lib/traits/Node.php @@ -54,7 +54,7 @@ trait Node { // The spec would have us iterate through the provided nodes and then iterate // through them again to append. Let's optimize this a wee bit, shall we? $document = ($this instanceof Document) ? $this : $this->ownerDocument; - $node = ($node->length > 1) ? $document->createDocumentFragment() : null; + $node = (count($nodes) > 1) ? $document->createDocumentFragment() : null; foreach ($nodes as $k => &$n) { // Can't do union types until PHP 8... OTL if (!$n instanceof \DOMNode && !is_string($n)) { diff --git a/lib/traits/ParentNode.php b/lib/traits/ParentNode.php index aac629a..7291376 100644 --- a/lib/traits/ParentNode.php +++ b/lib/traits/ParentNode.php @@ -115,7 +115,7 @@ trait ParentNode { } - protected function preInsertionValidity(\DOMNode $node, ?\DOMNode $child = null) { + protected function preInsertionValidity(\DOMNode $node, ?\DOMNode $child = null) { // "parent" in the spec comments below is $this # 1. If parent is not a Document, DocumentFragment, or Element node, then throw diff --git a/tests/cases/TestChildNode.php b/tests/cases/TestChildNode.php new file mode 100644 index 0000000..994ee87 --- /dev/null +++ b/tests/cases/TestChildNode.php @@ -0,0 +1,34 @@ +appendChild($d->createElement('html')); + $d->documentElement->appendChild($d->createElement('body')); + $div = $d->body->appendChild($d->createElement('div')); + $o = $d->body->appendChild($d->createTextNode('ook')); + $div2 = $d->body->appendChild($d->createElement('div')); + + // On node with parent + $div->after($d->createElement('span'), $o, $d->createElement('br')); + $this->assertSame('
ook
', (string)$d->body); + $div->after($o); + + // On node with no parent + $c = $d->createComment('ook'); + $this->assertNull($c->after($d->createTextNode('ook'))); + } +} \ No newline at end of file diff --git a/tests/cases/TestDocument.php b/tests/cases/TestDocument.php index 6ae8db0..2a76c73 100644 --- a/tests/cases/TestDocument.php +++ b/tests/cases/TestDocument.php @@ -85,6 +85,7 @@ class TestDocument extends \PHPUnit\Framework\TestCase { public function provideDisabledMethods(): iterable { return [ + [ 'createCDATASection', 'ook' ], [ 'createEntityReference', 'ook' ], [ 'loadXML', 'ook' ], [ 'saveXML', null ], @@ -348,6 +349,17 @@ class TestDocument extends \PHPUnit\Framework\TestCase { } + /** @covers \MensBeam\HTML\DOM\Document::importNode */ + public function testImportingNodesFailure() { + $this->expectException(DOMException::class); + $this->expectExceptionCode(DOMException::NOT_SUPPORTED); + $d = new \DOMDocument(); + $c = $d->createCDATASection('fail'); + $d2 = new Document(); + $d2->importNode($c); + } + + /** @covers \MensBeam\HTML\DOM\Document::__get_body */ public function testPropertyGetBody(): void { $d = new Document(); diff --git a/tests/cases/TestElementMap.php b/tests/cases/TestElementMap.php new file mode 100644 index 0000000..37b80bc --- /dev/null +++ b/tests/cases/TestElementMap.php @@ -0,0 +1,39 @@ +createElement('template'); + $this->assertTrue(ElementMap::add($t)); + $this->assertFalse(ElementMap::add($t)); + } + + + /** @covers \MensBeam\HTML\DOM\ElementMap::delete */ + public function testDelete(): void { + $d = new Document(); + $t = $d->createElement('template'); + $this->assertTrue(ElementMap::add($t)); + $this->assertTrue(ElementMap::delete($t)); + $this->assertFalse(ElementMap::delete($t)); + } +} \ No newline at end of file diff --git a/tests/cases/TestException.php b/tests/cases/TestException.php index 467ac11..6c79341 100644 --- a/tests/cases/TestException.php +++ b/tests/cases/TestException.php @@ -9,11 +9,8 @@ declare(strict_types=1); namespace MensBeam\HTML\DOM\TestCase; use MensBeam\HTML\DOM\{ - Document, - DocumentFragment, DOMException, - Exception, - HTMLTemplateElement + Exception }; use MensBeam\HTML\Parser; diff --git a/tests/cases/TestLeafNode.php b/tests/cases/TestLeafNode.php new file mode 100644 index 0000000..08553f2 --- /dev/null +++ b/tests/cases/TestLeafNode.php @@ -0,0 +1,49 @@ +appendChild($d->createElement('fail')); + } ], + [ function($d, $n) { + $n->insertBefore($d->createElement('fail')); + } ], + [ function($d, $n) { + $n->removeChild($d->createElement('fail')); + } ], + [ function($d, $n) { + $n->replaceChild($d->createElement('fail2'), $d->createElement('fail')); + } ], + ]; + } + + /** + * @dataProvider provideDisabledMethods + * @covers \MensBeam\HTML\DOM\LeafNode::appendChild + * @covers \MensBeam\HTML\DOM\LeafNode::insertBefore + * @covers \MensBeam\HTML\DOM\LeafNode::removeChild + * @covers \MensBeam\HTML\DOM\LeafNode::replaceChild + */ + public function testDisabledMethods(\Closure $closure): void { + $this->expectException(DOMException::class); + $this->expectExceptionCode(DOMException::HIERARCHY_REQUEST_ERROR); + $d = new Document(); + $closure($d, $d->createTextNode('ook')); + } +} \ No newline at end of file diff --git a/tests/phpunit.dist.xml b/tests/phpunit.dist.xml index be44b00..4d4ce53 100644 --- a/tests/phpunit.dist.xml +++ b/tests/phpunit.dist.xml @@ -16,10 +16,13 @@ + cases/TestChildNode.php cases/TestDocument.php cases/TestDocumentFragment.php cases/TestElement.php + cases/TestElementMap.php cases/TestException.php + cases/TestLeafNode.php cases/TestParentNode.php