Browse Source

Filled out ChildNode, added tests

master
Dustin Wilson 2 years ago
parent
commit
2ddeefbbcb
  1. 2
      lib/CharacterData.php
  2. 123
      lib/ChildNode.php
  3. 2
      lib/Inner/Document.php
  4. 40
      lib/Node.php
  5. 98
      lib/ParentNode.php
  6. 67
      tests/cases/TestChildNode.php
  7. 2
      tests/cases/TestElement.php
  8. 2
      tests/cases/TestNode.php
  9. 261
      tests/cases/TestParentNode.php
  10. 1
      tests/phpunit.dist.xml

2
lib/CharacterData.php

@ -10,6 +10,8 @@ namespace MensBeam\HTML\DOM;
abstract class CharacterData extends Node {
use ChildNode;
protected function __get_data(): string {
// PHP's DOM does this correctly already.
return $this->innerNode->data;

123
lib/ChildNode.php

@ -14,4 +14,127 @@ use MensBeam\HTML\DOM\Inner\{
trait ChildNode {
public function after(Node|string ...$nodes): void {
// After exists in PHP DOM, but it can insert incorrect nodes because of PHP
// DOM's incorrect (for HTML) pre-insertion validation.
# The after(nodes) method steps are:
#
# 1. Let parent be this’s parent.
$inner = $this->innerNode;
$parent = $this->parentNode;
# 2. If parent is null, then return.
if ($parent === null) {
return;
}
# 3. Let viableNextSibling be this’s first following sibling not in nodes;
# otherwise null.
$n = $inner;
$viableNextSibling = null;
while ($n = $n->nextSibling) {
foreach ($nodes as $nodeOrString) {
if ($nodeOrString instanceof Node && $this->getInnerNode($nodeOrString) === $n) {
continue 2;
}
}
$viableNextSibling = $n;
break;
}
# 4. Let node be the result of converting nodes into a node, given nodes and this’s
# node document.
$node = $this->convertNodesToNode($nodes);
# 5. Pre-insert node into parent before viableNextSibling.
$parent->insertBefore($node, ($viableNextSibling !== null) ? $inner->ownerDocument->getWrapperNode($viableNextSibling) : null);
}
public function before(Node|string ...$nodes): void {
// Before exists in PHP DOM, but it can insert incorrect nodes because of PHP
// DOM's incorrect (for HTML) pre-insertion validation.
# The before(nodes) method steps are:
#
# 1. Let parent be this’s parent.
$inner = $this->innerNode;
$parent = $this->parentNode;
# 2. If parent is null, then return.
if ($parent === null) {
return;
}
# 3. Let viablePreviousSibling be this’s first preceding sibling not in nodes;
# otherwise null.
$n = $inner;
$viablePreviousSibling = null;
while ($n = $n->previousSibling) {
foreach ($nodes as $nodeOrString) {
if ($nodeOrString instanceof Node && $this->getInnerNode($nodeOrString) === $n) {
continue 2;
}
}
$viablePreviousSibling = $n;
break;
}
# 4. Let node be the result of converting nodes into a node, given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 5. If viablePreviousSibling is null, then set it to parent’s first child;
# otherwise to viablePreviousSibling’s next sibling.
$viablePreviousSibling = ($viablePreviousSibling === null) ? $parent->firstChild : $inner->ownerDocument->getWrapperNode($viablePreviousSibling->nextSibling);
# 6. Pre-insert node into parent before viablePreviousSibling.
$parent->insertBefore($node, $viablePreviousSibling);
}
public function replaceWith(Node|string ...$nodes): void {
// Before exists in PHP DOM, but it can insert incorrect nodes because of PHP
// DOM's incorrect (for HTML) pre-insertion validation.
# The replaceWith(nodes) method steps are:
#
# 1. Let parent be this’s parent.
$inner = $this->innerNode;
$parent = $this->parentNode;
# 2. If parent is null, then return.
if ($parent === null) {
return;
}
# 3. Let viableNextSibling be this’s first following sibling not in nodes;
# otherwise null.
$n = $inner;
$viableNextSibling = null;
while ($n = $n->nextSibling) {
foreach ($nodes as $nodeOrString) {
if ($nodeOrString instanceof Node && $this->getInnerNode($nodeOrString) === $n) {
continue 2;
}
}
$viableNextSibling = $n;
break;
}
# 4. Let node be the result of converting nodes into a node, given nodes and
# this’s node document.
$node = $this->convertNodesToNode($nodes);
# 5. If this’s parent is parent, replace this with node within parent.
# Note: This could have been inserted into node.
if ($this->parentNode === $parent) {
$parent->replaceChild($node, $this);
}
# 6. Otherwise, pre-insert node into parent before viableNextSibling.
else {
$parent->insertBefore($node, ($viableNextSibling !== null) ? $inner->ownerDocument->getWrapperNode($viableNextSibling) : null);
}
}
}

2
lib/Inner/Document.php

@ -112,7 +112,7 @@ class Document extends \DOMDocument {
$className = 'HTMLTemplateElement';
}
// This is done until we do element classes
elseif (in_array($name, [ 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'i', 'iframe', 'img', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'marquee', 'math', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'portal', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'svg', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr' ])) {
elseif (in_array($name, [ 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'marquee', 'math', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'portal', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'svg', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr' ])) {
$className = 'HTMLElement';
}
# If name is a valid custom element name, then return HTMLElement.

40
lib/Node.php

@ -981,6 +981,33 @@ abstract class Node {
return false;
}
protected function convertNodesToNode(array $nodes): Node {
# To convert nodes into a node, given nodes and document, run these steps:
# 1. Let node be null.
# 2. Replace each string in nodes with a new Text node whose data is the string
# and node document is document.
# 3. If nodes contains one node, then set node to nodes[0].
# 4. Otherwise, set node to a new DocumentFragment node whose node document is
# document, and then append each node in nodes, if any, to it.
// 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?
$doc = (!$this instanceof Document) ? $this->ownerDocument : $this;
$node = (count($nodes) !== 1) ? $doc->createDocumentFragment() : null;
foreach ($nodes as $k => $n) {
if (is_string($n)) {
$n = $doc->createTextNode($n);
}
if ($node !== null) {
$node->appendChild($n);
} else {
$node = $n;
}
}
return $node;
}
protected function getInnerDocument(): InnerDocument {
return ($this instanceof Document) ? $this->innerNode : $this->innerNode->ownerDocument;
}
@ -1231,18 +1258,9 @@ abstract class Node {
// below walks through this node and temporarily replaces foreign descendants
// with bullshit elements which are then replaced once the node is inserted.
if ($element->namespaceURI === null && ($this instanceof DocumentFragment || $this->getRootNode() !== null) && $element->hasChildNodes()) {
// XPath can't easily match just unprefixed elements, so we have to do this the
// old fashioned way by walking the DOM.
$foreign = $this->walkInner($element, function(\DOMNode $n) {
if ($n instanceof \DOMElement && ($n->parentNode !== null && $n->parentNode->namespaceURI === null) && $n->namespaceURI !== null && $n->prefix === '') {
return self::WALK_ACCEPT | self::WALK_SKIP_CHILDREN;
}
return self::WALK_REJECT;
});
$foreign = $element->ownerDocument->xpath->query('.//*[parent::*[namespace-uri()=""] and not(namespace-uri()="") and name()=local-name()]', $element);
$this->bullshitReplacements = [];
if ($foreign->current() !== null) {
if ($foreign->length > 0) {
$count = 0;
$doc = $this->getInnerDocument();
foreach ($foreign as $f) {

98
lib/ParentNode.php

@ -16,6 +16,64 @@ use Symfony\Component\CssSelector\CssSelectorConverter,
trait ParentNode {
protected function __get_childElementCount(): int {
return $this->innerNode->childElementCount;
}
protected function __get_children(): HTMLCollection {
$doc = $this->getInnerDocument();
// HTMLCollections cannot be created from their constructors normally.
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\HTMLCollection', $doc, $doc->xpath->query('.//*', $this->innerNode));
}
protected function __get_firstElementChild(): ?Element {
$result = $this->innerNode->firstElementChild;
return ($result !== null) ? $this->getInnerDocument()->getWrapperNode($result) : null;
}
protected function __get_lastElementChild(): ?Element {
$result = $this->innerNode->lastElementChild;
return ($result !== null) ? $this->getInnerDocument()->getWrapperNode($result) : null;
}
public function append(Node|string ...$nodes): void {
# The append(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and this’s
# node document.
$node = $this->convertNodesToNode($nodes);
# 2. Append node to this.
$this->appendChild($node);
}
public function prepend(Node|string ...$nodes): void {
# The prepend(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and this’s
# node document.
$node = $this->convertNodesToNode($nodes);
# 2. Pre-insert node into this before this’s first child.
$this->insertBefore($node, $this->firstChild);
}
public function replaceChildren(Node|string ...$nodes): void {
# The prepend(nodes) method steps are:
# 1. Let node be the result of converting nodes into a node given nodes and this’s
# node document.
$node = $this->convertNodesToNode($nodes);
# 2. Ensure pre-insertion validity of node into this before null.
$this->preInsertionValidity($node);
# 3. Replace all with node within this.
while ($this->innerNode->hasChildNodes()) {
$this->innerNode->removeChild($this->innerNode->firstChild);
}
$this->appendChild($node);
}
public function querySelector(string $selectors): ?Element {
# The querySelector(selectors) method steps are to return the first result of
# running scope-match a selectors string selectors against this, if the result
@ -42,11 +100,11 @@ trait ParentNode {
*/
public function walk(?\Closure $filter = null, bool $includeReferenceNode = false): \Generator {
if ($this instanceof DocumentFragment || (!$this instanceof DocumentFragment && !$includeReferenceNode)) {
$node = $node->firstChild;
$node = $this->innerNode->firstChild;
}
if ($node !== null) {
$doc = (!$node instanceof InnerDocument) ? $node->ownerDocument : $node;
$doc = $this->getInnerDocument();
do {
$next = $node->nextSibling;
@ -63,11 +121,11 @@ trait ParentNode {
continue 2;
case Node::WALK_REJECT:
break;
default: return;
default: throw new DOMException(DOMException::SYNTAX_ERROR);
}
if ($node->parentNode !== null && $node->hasChildNodes()) {
yield from $node->walk($filter);
yield from $wrapperNode->walk($filter);
}
} while ($node = $next);
}
@ -99,36 +157,4 @@ trait ParentNode {
$nodeList = $this->getInnerDocument()->xpath->query($s, $this->innerNode);
return $nodeList;
}
protected function walkInner(\DOMNode $node, ?\Closure $filter = null, bool $includeReferenceNode = false): \Generator {
if (!$node instanceof DocumentFragment && !$includeReferenceNode) {
$node = $node->firstChild;
}
if ($node !== null) {
$doc = (!$node instanceof InnerDocument) ? $node->ownerDocument : $node;
do {
$next = $node->nextSibling;
$result = ($filter === null) ? Node::WALK_ACCEPT : $filter($node);
switch ($result) {
case Node::WALK_ACCEPT:
yield $node;
break;
case Node::WALK_ACCEPT | Node::WALK_SKIP_CHILDREN:
yield $node;
case Node::WALK_REJECT | Node::WALK_SKIP_CHILDREN:
continue 2;
case Node::WALK_REJECT:
break;
default: return;
}
if ($node->parentNode !== null && $node->hasChildNodes()) {
yield from $this->walkInner($node, $filter);
}
} while ($node = $next);
}
}
}

67
tests/cases/TestChildNode.php

@ -0,0 +1,67 @@
<?php
/**
* @license MIT
* Copyright 2017 Dustin Wilson, J. King, et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\HTML\DOM\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
Element,
Node
};
/** @covers \MensBeam\HTML\DOM\ChildNode */
class TestChildNode extends \PHPUnit\Framework\TestCase {
public function testMethod_after_before_replaceWith(): void {
$d = new Document();
$d->appendChild($d->createElement('html'));
$body = $d->documentElement->appendChild($d->createElement('body'));
$div = $body->appendChild($d->createElement('div'));
$o = $body->appendChild($d->createTextNode('ook'));
$div2 = $body->appendChild($d->createElement('div'));
// On node with parent
$div->after($d->createElement('span'), $o, 'eek');
$this->assertSame('<body><div></div><span></span>ookeek<div></div></body>', (string)$body);
$div->after($o);
$this->assertSame('<body><div></div>ook<span></span>eek<div></div></body>', (string)$body);
// On node with no parent
$c = $d->createComment('ook');
$this->assertNull($c->after($d->createTextNode('ook')));
// On node with parent
$br = $body->insertBefore($d->createElement('br'), $div);
$e = $d->createTextNode('eek');
$div->before($d->createElement('span'), $o, 'eek', $e, $br);
$this->assertSame('<body><span></span>ookeekeek<br><div></div><span></span>eek<div></div></body>', (string)$body);
$div->before($o);
$this->assertSame('<body><span></span>eekeek<br>ook<div></div><span></span>eek<div></div></body>', (string)$body);
// On node with no parent
$c = $d->createComment('ook');
$this->assertNull($c->before($d->createTextNode('ook')));
// On node with parent
$s = $d->createElement('span');
$br->replaceWith('ack', $o, $e, $s);
$this->assertSame('<body><span></span>eekackookeek<span></span><div></div><span></span>eek<div></div></body>', (string)$body);
$s->replaceWith($o);
$this->assertSame('<body><span></span>eekackeekook<div></div><span></span>eek<div></div></body>', (string)$body);
// On node with no parent
$c = $d->createComment('ook');
$this->assertNull($c->replaceWith($d->createTextNode('ook')));
// Parent within node
$o->replaceWith('poo', $o, $e);
$this->assertSame('<body><span></span>eekackpooookeek<div></div><span></span>eek<div></div></body>', (string)$body);
}
}

2
tests/cases/TestElement.php

@ -422,10 +422,10 @@ class TestElement extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\ParentNode::walkInner
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has

2
tests/cases/TestNode.php

@ -66,11 +66,11 @@ class TestNode extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\ParentNode::walkInner
* @covers \MensBeam\HTML\DOM\ProcessingInstruction::__construct
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has

261
tests/cases/TestParentNode.php

@ -11,12 +11,148 @@ namespace MensBeam\HTML\DOM\TestCase;
use MensBeam\HTML\DOM\{
Document,
DOMException,
Element,
Node
};
/** @covers \MensBeam\HTML\DOM\ParentNode */
class TestParentNode extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\ParentNode::append
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_innerHTML
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::convertNodesToNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_append(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$eek = $d->getElementById('eek');
$eek->append('ook', $d->createElement('br'));
$eek->append('eek');
$this->assertSame('eekook<br>eek', $eek->innerHTML);
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::prepend
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_innerHTML
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_firstChild
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::convertNodesToNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::insertBefore
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_prepend(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$eek = $d->getElementById('eek');
$eek->prepend('ook', $d->createElement('br'));
$this->assertSame('ook<br>eek', $eek->innerHTML);
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::replaceChildren
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createDocumentFragment
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\Document::createTextNode
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentFragment::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_innerHTML
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::convertNodesToNode
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_replaceChildren(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$eek = $d->getElementById('eek');
$eek->replaceChildren('ook', $d->createElement('br'));
$this->assertSame('ook<br>', $eek->innerHTML);
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::querySelector
* @covers \MensBeam\HTML\DOM\ParentNode::querySelectorAll
@ -84,4 +220,129 @@ class TestParentNode extends \PHPUnit\Framework\TestCase {
$d = new Document();
$d->querySelector('fail?');
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::walk
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DocumentType::__construct
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @covers \MensBeam\HTML\DOM\Node::postParsingTemplatesFix
* @covers \MensBeam\HTML\DOM\Text::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::__get_xpath
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::setProtectedProperties
*/
public function testMethod_walk(): void {
$d = new Document(<<<HTML
<!DOCTYPE html>
<html>
<body>
<div>ook</div>
<div>
<div><p>Eek</p></div>
<div>
<div><p>Ook</p></div>
</div>
</div>
</body>
</html>
HTML);
// Empty filter -- walk over all nodes
$w = $d->walk();
$this->assertSame($d->doctype, $w->current());
foreach ($w as $node);
// Simple accept on element and reject everything else filter
$w = $d->walk(function($n) {
return ($n instanceof Element) ? Node::WALK_ACCEPT : Node::WALK_REJECT;
});
$this->assertSame($d->documentElement, $w->current());
foreach ($w as $node);
// Accept element but ignore children, simple reject otherwise
$w = $d->walk(function($n) {
return ($n instanceof Element) ? Node::WALK_ACCEPT | Node::WALK_SKIP_CHILDREN : Node::WALK_REJECT;
});
$this->assertSame($d->documentElement, $w->current());
$this->assertNull($w->next());
}
/**
* @covers \MensBeam\HTML\DOM\ParentNode::walk
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMException::__construct
* @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::getInnerDocument
* @covers \MensBeam\HTML\DOM\Node::getInnerNode
* @covers \MensBeam\HTML\DOM\Node::getRootNode
* @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes
* @covers \MensBeam\HTML\DOM\Node::preInsertionValidity
* @covers \MensBeam\HTML\DOM\Inner\Document::__construct
* @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::get
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::has
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::key
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testMethod_walk__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::SYNTAX_ERROR);
$d = new Document();
$d->appendChild($d->createElement('html'));
$w = $d->walk(function($n) {
return 2112;
});
$w->current();
}
public function testProperty_childElementCount(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$this->assertEquals(2, $d->body->childElementCount);
}
public function testProperty_children(): void {
$d = new Document('<!DOCTYPE html><html><body><div>ook</div><div id="eek">eek</div></body></html>');
$this->assertEquals(2, $d->body->children->length);
}
public function testProperty_firstElementChild(): void {
$d = new Document('<!DOCTYPE html><html><body>ook<div id="ook">ook</div><div id="eek">eek</div></body></html>');
$body = $d->body;
$this->assertSame($d->getElementById('ook'), $body->firstElementChild);
$this->assertNull($d->getElementById('eek')->firstElementChild);
}
public function testProperty_lastElementChild(): void {
$d = new Document('<!DOCTYPE html><html><body>ook<div id="ook">ook</div><div id="eek">eek</div><div id="ack">ack</div></body></html>');
$body = $d->body;
$this->assertSame($d->getElementById('ack'), $body->lastElementChild);
$this->assertNull($d->getElementById('eek')->lastElementChild);
}
}

1
tests/phpunit.dist.xml

@ -18,6 +18,7 @@
<testsuite name="DOM">
<file>cases/TestAttr.php</file>
<file>cases/TestCharacterData.php</file>
<file>cases/TestChildNode.php</file>
<file>cases/TestDocument.php</file>
<file>cases/TestDocumentOrElement.php</file>
<file>cases/TestDOMImplementation.php</file>

Loading…
Cancel
Save