diff --git a/lib/Element.php b/lib/Element.php
index 4b45fcb..d710ba0 100644
--- a/lib/Element.php
+++ b/lib/Element.php
@@ -193,6 +193,77 @@ class Element extends Node {
$parent->replaceChild($fragment, $this);
}
+ protected function __get_outerText(): ?string {
+ # The innerText and outerText getter steps are:
+ # 1. If this is not being rendered or if the user agent is a non-CSS user agent,
+ # then return this's descendant text content.
+ // This is a non-CSS user agent. Nothing else to do here.
+ return $this->__get_textContent();
+ }
+
+ protected function __set_outerText(string $value): void {
+ # The outerText setter steps are:
+ # 1. If this's parent is null, then throw a "NoModificationAllowedError"
+ # DOMException.
+ $innerNode = $this->innerNode;
+ if ($this->parentNode === null) {
+ throw new DOMException(DOMException::NO_MODIFICATION_ALLOWED);
+ }
+
+ # 2. Let next be this's next sibling.
+ $next = $innerNode->nextSibling;
+
+ # 3. Let previous be this's previous sibling.
+ $previous = $innerNode->previousSibling;
+
+ # 4. Let fragment be the rendered text fragment for the given value given this's node
+ # document.
+ $fragment = $this->getRenderedTextFragment($value);
+
+ # 5. Replace this with fragment within this's parent.
+ // Check for child nodes before appending to prevent a stupid warning.
+ if ($fragment->hasChildNodes()) {
+ $innerNode->parentNode->replaceChild($fragment, $innerNode);
+ } else {
+ $innerNode->parentNode->removeChild($innerNode);
+ }
+
+ # 6. If next is non-null and next's previous sibling is a Text node, then merge
+ # with the next text node given next's previous sibling.
+ if ($next !== null && $next->previousSibling instanceof \DOMText) {
+ # To merge with the next text node given a Text node node:
+ # 1. Let next be node's next sibling.
+ # 2. If next is not a Text node, then return.
+ // Already checked for
+
+ # 3. Replace data with node, node's data's length, 0, and next's data.
+ $next->previousSibling->data .= $next->data;
+
+ # 4. If next's parent is non-null, then remove next.
+ // DEVIATION: There are no mutation events in this implementation, so there's no
+ // reason to check for a parent here.
+ $next->parentNode->removeChild($next);
+ }
+
+ # 7. If previous is a Text node, then merge with the next text node given previous.
+ if ($previous instanceof \DOMText) {
+ # To merge with the next text node given a Text node node:
+ # 1. Let next be node's next sibling.
+ $next = $previous->nextSibling;
+
+ # 2. If next is not a Text node, then return.
+ if ($next instanceof \DOMText) {
+ # 3. Replace data with node, node's data's length, 0, and next's data.
+ $previous->data .= $next->data;
+
+ # 4. If next's parent is non-null, then remove next.
+ // DEVIATION: There are no mutation events in this implementation, so there's no
+ // reason to check for a parent here.
+ $next->parentNode->removeChild($next);
+ }
+ }
+ }
+
protected function __get_prefix(): ?string {
$prefix = $this->innerNode->prefix;
return ($prefix !== '') ? $prefix : null;
diff --git a/lib/Inner/Document.php b/lib/Inner/Document.php
index 4912c2f..3ac4fde 100644
--- a/lib/Inner/Document.php
+++ b/lib/Inner/Document.php
@@ -53,7 +53,8 @@ class Document extends \DOMDocument {
$this->_wrapperNode = \WeakReference::create($wrapperNode);
if (self::$parentNamespace === null) {
- self::$parentNamespace = substr(__NAMESPACE__, 0, strrpos(__NAMESPACE__, '\\'));
+ // This line is covered, but pcov declares it not covered for some reason...
+ self::$parentNamespace = substr(__NAMESPACE__, 0, strrpos(__NAMESPACE__, '\\')); // @codeCoverageIgnore
}
}
diff --git a/tests/cases/TestElement.php b/tests/cases/TestElement.php
index ce98ebb..75c074d 100644
--- a/tests/cases/TestElement.php
+++ b/tests/cases/TestElement.php
@@ -1375,7 +1375,12 @@ class TestElement extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\HTML\DOM\Element::__get_innerText
* @covers \MensBeam\HTML\DOM\Element::__set_innerText
+ * @covers \MensBeam\HTML\DOM\Element::__get_outerText
+ * @covers \MensBeam\HTML\DOM\Element::__set_outerText
*
+ * @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_body
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
@@ -1386,6 +1391,8 @@ class TestElement extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Element::__get_innerHTML
* @covers \MensBeam\HTML\DOM\Element::getRenderedTextFragment
* @covers \MensBeam\HTML\DOM\Node::__construct
+ * @covers \MensBeam\HTML\DOM\Node::__get_childNodes
+ * @covers \MensBeam\HTML\DOM\Node::__get_parentNode
* @covers \MensBeam\HTML\DOM\Node::__get_textContent
* @covers \MensBeam\HTML\DOM\Node::appendChild
* @covers \MensBeam\HTML\DOM\Node::getInnerDocument
@@ -1405,21 +1412,58 @@ class TestElement extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
- public function testProperty_innerText() {
+ public function testProperty_innerText_outerText() {
$d = new Document();
$d->appendChild($d->createElement('html'));
$d->documentElement->appendChild($d->createElement('body'));
- $s = $d->body->appendChild($d->createElement('span'));
+ $body = $d->body;
+ $body->appendChild($d->createTextNode('ook '));
+ $s = $body->appendChild($d->createElement('span'));
$s->appendChild($d->createTextNode('ook'));
- $this->assertSame('ook', $d->body->innerHTML);
-
- $d->body->innerText = <<appendChild($d->createTextNode(' eek'));
+ $this->assertSame('ook ook eek', $body->innerHTML);
+ $s->innerText = <<assertSame('ookook eek ook', $d->body->innerText);
- $this->assertSame('ook
ook eek ook', $d->body->innerHTML);
+ $this->assertSame('ook ookook eek ook eek', $body->innerText);
+ $this->assertSame('ook
ook eek ook', $s->innerHTML);
+
+ $s->outerText = 'ack';
+ $this->assertSame('ook ack eek', $body->outerText);
+ $this->assertEquals(1, $body->childNodes->length);
+
+ $s = $body->appendChild($d->createElement('span'));
+ $s->outerText = '';
+ $this->assertSame('ook ack eek', $body->outerText);
+ }
+
+
+ /**
+ * @covers \MensBeam\HTML\DOM\Element::__set_outerText
+ *
+ * @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::__get_parentNode
+ * @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
+ */
+ public function testProperty_outerText__errors() {
+ $this->expectException(DOMException::class);
+ $this->expectExceptionCode(DOMException::NO_MODIFICATION_ALLOWED);
+ $d = new Document();
+ $h = $d->createElement('html');
+ $h->outerText = 'fail';
}