Browse Source

More tests for HTMLElement

master
Dustin Wilson 2 years ago
parent
commit
12d1dba36c
  1. 7
      composer.json
  2. 12
      composer.lock
  3. 30
      lib/Document.php
  4. 96
      lib/HTMLElement.php
  5. 6
      lib/Inner/Document.php
  6. 133
      tests/cases/TestHTMLElement.php
  7. 5
      vendor-bin/phpunit/composer.lock
  8. 2
      vendor-bin/robo/composer.lock

7
composer.json

@ -59,5 +59,10 @@
"type": "git",
"url": "mensbeam-gitea:MensBeam/HTML-Parser.git"
}
]
],
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
}
}

12
composer.lock

@ -1367,16 +1367,16 @@
},
{
"name": "scrivo/highlight.php",
"version": "v9.18.1.8",
"version": "v9.18.1.9",
"source": {
"type": "git",
"url": "https://github.com/scrivo/highlight.php.git",
"reference": "6d5049cd2578e19a06adbb6ac77879089be1e3f9"
"reference": "d45585504777e6194a91dffc7270ca39833787f8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/scrivo/highlight.php/zipball/6d5049cd2578e19a06adbb6ac77879089be1e3f9",
"reference": "6d5049cd2578e19a06adbb6ac77879089be1e3f9",
"url": "https://api.github.com/repos/scrivo/highlight.php/zipball/d45585504777e6194a91dffc7270ca39833787f8",
"reference": "d45585504777e6194a91dffc7270ca39833787f8",
"shasum": ""
},
"require": {
@ -1441,7 +1441,7 @@
"type": "github"
}
],
"time": "2021-10-24T00:28:14+00:00"
"time": "2021-12-03T06:45:28+00:00"
},
{
"name": "symfony/console",
@ -2808,5 +2808,5 @@
"ext-dom": "*"
},
"platform-dev": [],
"plugin-api-version": "2.1.0"
"plugin-api-version": "2.2.0"
}

30
lib/Document.php

@ -28,6 +28,7 @@ class Document extends Node implements \ArrayAccess {
protected string $_characterSet = 'UTF-8';
protected string $_compatMode = 'CSS1Compat';
protected string $_contentType = 'text/html';
protected bool $designModeEnabled = false;
protected DOMImplementation $_implementation;
protected string $_URL = 'about:blank';
@ -68,6 +69,35 @@ class Document extends Node implements \ArrayAccess {
return $this->_contentType;
}
protected function __get_designMode(): string {
# The designMode getter steps are to return "on" if this's design mode enabled
# is true; otherwise "off".
return ($this->designModeEnabled) ? 'on' : 'off';
}
protected function __set_designMode(string $value): void {
# The designMode setter steps are:
# 1. Let value be the given value, converted to ASCII lowercase.
$value = strtolower($value);
# 2. If value is "on" and this's design mode enabled is false, then:
if ($value === 'on' && !$this->designModeEnabled) {
# 1. Set this's design mode enabled to true.
$this->designModeEnabled = true;
# 2. Reset this's active range's start and end boundary points to be at the start of this.
// Ranges aren't implemented.
# 3. Run the focusing steps for this's document element, if non-null.
// There's nothing to do here; there's no chance for a focusing element.
}
# 3. If value is "off", then set this's design mode enabled to false.
elseif ($value === 'off') {
$this->designModeEnabled = false;
}
}
protected function __get_dir(): string {
# The dir IDL attribute on Document objects must reflect the dir content attribute
# of the html element, if any, limited to only known values. If there is no such

96
lib/HTMLElement.php

@ -25,19 +25,39 @@ class HTMLElement extends Element {
# The autocapitalize getter steps are to:
#
# 1. Let state be the own autocapitalization hint of this.
$state = $this->autoCapitalizationHint($this);
$state = $this->autoCapitalizationHint($this->innerNode);
# 2. If state is default, then return the empty string.
if ($state === 'off') {
return '';
}
# 3. If state is none, then return "none".
if ($state === 'none') {
# 4. If state is sentences, then return "sentences".
# 5. Return the keyword value corresponding to state.
// Switch below will handle all of these steps.
# The autocapitalize attribute is an enumerated attribute whose states are the
# possible autocapitalization hints. The autocapitalization hint specified by
# the attribute's state combines with other considerations to form the used
# autocapitalization hint, which informs the behavior of the user agent. The
# keywords for this attribute and their state mappings are as follows:
switch ($state) {
case 'default':
return '';
case 'none':
case 'sentences':
case 'words':
case 'characters':
return $state;
case 'off':
return 'none';
case 'on':
# The invalid value default is the sentences state.
default: return 'sentences';
}
}
protected function __set_autocapitalize(string $value): void {
# The autocapitalize setter steps are to set the autocapitalize content
# attribute to the given value.
$this->setAttribute('autocapitalize', $value);
}
protected function __get_contentEditable(): string {
@ -63,7 +83,7 @@ class HTMLElement extends Element {
# be set to the string "false", and otherwise the attribute setter must throw a
# "SyntaxError" DOMException.
$value = strtolower($this->getAttribute('contenteditable'));
$value = strtolower($this->getAttribute('contenteditable') ?? '');
return ($value === 'true' || $value === 'false') ? $value : 'inherit';
}
@ -153,7 +173,7 @@ class HTMLElement extends Element {
}
$data = $this->getAttribute('data');
if ($data !== null && in_array(strtolower(substr(strrchr($fileName, '.'), 1)), [ 'apng', 'avif', 'gif', 'jpeg', 'jpg', 'png', 'svg', 'webp' ])) {
if ($data !== null && str_contains(needle: '.', haystack: $data) && in_array(strtolower(substr(strrchr($data, '.'), 1)), [ 'apng', 'avif', 'gif', 'jpeg', 'jpg', 'png', 'svg', 'webp' ])) {
return true;
}
}
@ -196,15 +216,18 @@ class HTMLElement extends Element {
protected function __get_hidden(): bool {
# The hidden getter steps are to return true if this's visibility state is
# "hidden", otherwise false.
// There's no visibility state in this implementation because there's nothing to
// render. The only way for the visibility state to be off is if the element is
// hidden via the hidden attribute.
$value = ($this->getAttribute('hidden') !== null);
if ($value) {
if ($this->hasAttribute('hidden')) {
return true;
}
$n = $this;
$n = $this->innerNode;
$doc = $n->ownerDocument;
while ($n = $n->parentNode) {
if (property_exists($n, 'hidden') && ($n->getAttribute('hidden') !== null)) {
if ($doc->getWrapperNode($n) instanceof HTMLElement && $n->hasAttribute('hidden')) {
return true;
}
}
@ -280,7 +303,7 @@ class HTMLElement extends Element {
$this->setAttribute('inputmode', $value);
}
protected function __get_isContentEditable(): string {
protected function __get_isContentEditable(): bool {
# The isContentEditable IDL attribute, on getting, must return true if the
# element is either an editing host or editable, and false otherwise.
@ -294,21 +317,24 @@ class HTMLElement extends Element {
# math element, or it is not an Element and its parent is an HTML element.
$doc = ($this instanceof Document) ? $this : $this->ownerDocument;
if ($doc->designMode) {
if ($doc->designMode === 'on') {
return true;
}
$value = strtolower($this->getAttribute('contenteditable'));
if ($value === 'true') {
return true;
} elseif ($value === 'false') {
return false;
$value = $this->getAttribute('contenteditable');
if ($value !== null) {
$value = strtolower($value);
if ($value === 'true') {
return true;
} elseif ($value === 'false') {
return false;
}
}
if ($doc !== this) {
$n = $this;
while ($n = $n->parentNode) {
if (property_exists($n, 'contentEditable') && $n->contentEditable === 'true') {
$n = $this->innerNode;
while ($n = $n->parentNode) {
if ($n instanceof \DOMElement) {
if ($n->getAttribute('contenteditable') === 'true' && $n->ownerDocument->getWrapperNode($n) instanceof HTMLElement) {
return true;
}
}
@ -449,9 +475,10 @@ class HTMLElement extends Element {
return false;
}
$n = $this;
$n = $this->innerNode;
$doc = $n->ownerDocument;
while ($n = $n->parentNode) {
if (property_exists($n, 'translate') && $n->getAttribute('translate') === 'yes') {
if ($n->getAttribute('translate') === 'yes' && $doc->getWrapperNode($n) instanceof HTMLElement) {
return true;
}
}
@ -466,19 +493,19 @@ class HTMLElement extends Element {
}
protected function autoCapitalizationHint(HTMLElement $element): string {
protected function autoCapitalizationHint(\DOMElement $element): string {
# To compute the own autocapitalization hint of an element element, run the
# following steps:
# 1. If the autocapitalize content attribute is present on element, and its
# value is not the empty string, return the state of the attribute.
$value = $this->getAttribute('autocapitalize');
$value = $element->getAttribute('autocapitalize');
if ($value !== null && $value !== '') {
return $value;
}
# 2. If element is an autocapitalize-inheriting element and has a non-null form
# owner, return the own autocapitalization hint of element's form owner.
elseif (in_array($this->tagName, [ 'button', 'fieldset', 'input', 'output', 'select', 'textarea' ])) {
elseif (in_array($element->tagName, [ 'button', 'fieldset', 'input', 'output', 'select', 'textarea' ])) {
# A form-associated element can have a relationship with a form element, which
# is called the element's form owner. If a form-associated element is not
# associated with a form element, its form owner is said to be null.
@ -487,16 +514,15 @@ class HTMLElement extends Element {
# form element (as described below), but, if it is listed, may have a form
# attribute specified to override this.
$n = $this;
while ($n = $this->parentNode) {
if ($n instanceof HTMLElement && $n->tagName === 'form') {
$n = $element;
while ($n = $n->parentNode) {
if ($n->tagName === 'form' && $n->ownerDocument->getWrapperNode($n) instanceof HTMLElement) {
return $this->autoCapitalizationHint($n);
}
}
}
## 3. Return default.
// The default of this user agent is 'off'
return 'off';
return 'default';
}
}

6
lib/Inner/Document.php

@ -61,9 +61,9 @@ class Document extends \DOMDocument {
public function getWrapperNode(\DOMNode $node): ?WrapperNode {
// If the node is a Document then the wrapperNode is this's wrapperNode
// property.
if ($node instanceof Document) {
// If the node is this document then return the wrapper node; it's already
// known.
if ($node === $this) {
return $this->wrapperNode;
}

133
tests/cases/TestHTMLElement.php

@ -16,6 +16,124 @@ use MensBeam\HTML\DOM\{
/** @covers \MensBeam\HTML\DOM\HTMLElement */
class TestHTMLElement extends \PHPUnit\Framework\TestCase {
public function testProperty_accessKey(): void {
$d = new Document('<!DOCTYPE html><html><body><a id="ook" href="https://ook.com" accesskey="o"></a></body></html>');
$ook = $d->getElementById('ook');
$this->assertSame('o', $ook->accessKey);
$ook->accessKey = 'e';
$this->assertSame('e', $ook->accessKey);
}
public function testProperty_autocapitalize(): void {
$d = new Document('<!DOCTYPE html><html><body><form><input type="text" autocapitalize="on"></form></body></html>');
$ook = $d->getElementsByTagName('input')[0];
$this->assertSame('sentences', $ook->autocapitalize);
$ook->removeAttribute('autocapitalize');
$this->assertSame('', $ook->autocapitalize);
$ook->autocapitalize = 'words';
$this->assertSame('words', $ook->autocapitalize);
$ook->autocapitalize = 'off';
$this->assertSame('none', $ook->autocapitalize);
$form = $d->getElementsByTagName('form')[0];
$form->autocapitalize = 'bullshit';
$ook->removeAttribute('autocapitalize');
$this->assertSame('sentences', $ook->autocapitalize);
}
public function testProperty_contentEditable_isContentEditable(): void {
$d = new Document('<!DOCTYPE html><html><body><div></div></body></html>');
$div = $d->getElementsByTagName('div')[0];
$this->assertSame('inherit', $div->contentEditable);
$this->assertFalse($div->isContentEditable);
$div->contentEditable = 'true';
$this->assertSame('true', $div->contentEditable);
$this->assertTrue($div->isContentEditable);
$div->contentEditable = 'false';
$this->assertSame('false', $div->contentEditable);
$this->assertFalse($div->isContentEditable);
$div->contentEditable = 'inherit';
$this->assertFalse($div->hasAttribute('contenteditable'));
$this->assertSame('inherit', $div->contentEditable);
$div->removeAttribute('contenteditable');
$d->body->contentEditable = 'true';
$this->assertSame('inherit', $div->contentEditable);
$this->assertTrue($div->isContentEditable);
$d->designMode = 'on';
$this->assertTrue($div->isContentEditable);
}
public function testProperty_contentEditable__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::SYNTAX_ERROR);
$d = new Document('<!DOCTYPE html><html></html>');
$d->documentElement->contentEditable = 'fail';
}
public function testProperty_dir(): void {
$d = new Document('<!DOCTYPE html><html dir="ltr"></html>');
$html = $d->documentElement;
$this->assertSame('ltr', $html->dir);
$html->dir = 'bullshit';
$this->assertSame('', $html->dir);
}
public function testProperty_draggable(): void {
$d = new Document('<!DOCTYPE html><html><body><img src="ook.html"><object data="ook"></object><div></div></body></html>');
$img = $d->getElementsByTagName('img')[0];
$div = $d->getElementsByTagName('div')[0];
$object = $d->getElementsByTagName('object')[0];
$this->assertTrue($img->draggable);
$this->assertFalse($div->draggable);
$div->draggable = true;
$this->assertTrue($div->draggable);
$div->draggable = false;
$this->assertFalse($div->draggable);
$img->draggable = false;
$this->assertFalse($img->draggable);
$this->assertFalse($object->draggable);
$object->setAttribute('type', 'image/jpeg');
$this->assertTrue($object->draggable);
$object->removeAttribute('type');
$object->setAttribute('data', 'ook.png');
$this->assertTrue($object->draggable);
}
public function testProperty_enterKeyHint(): void {
$d = new Document('<!DOCTYPE html><html></html>');
$html = $d->documentElement;
$this->assertSame('', $html->enterKeyHint);
$html->enterKeyHint = 'ook';
$this->assertSame('', $html->enterKeyHint);
$html->enterKeyHint = 'done';
$this->assertSame('done', $html->enterKeyHint);
}
public function testProperty_hidden(): void {
$d = new Document('<!DOCTYPE html><html><body><div></div></body></html>');
$div = $d->getElementsByTagName('div')[0];
$this->assertFalse($div->hidden);
$div->hidden = true;
$this->assertTrue($div->hidden);
$div->hidden = false;
$d->documentElement->hidden = true;
$this->assertTrue($div->hidden);
}
public function testProperty_lang(): void {
$d = new Document('<!DOCTYPE html><html></html>');
$html = $d->documentElement;
$this->assertSame('', $html->lang);
$html->lang = 'en';
$this->assertSame('en', $html->lang);
$html->lang = 'ook';
$this->assertSame('ook', $html->lang);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__get_innerText
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_innerText
@ -56,7 +174,7 @@ class TestHTMLElement extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
* @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty
*/
public function testProperty_innerText_outerText() {
public function testProperty_innerText_outerText(): void {
$d = new Document();
$d->appendChild($d->createElement('html'));
$d->documentElement->appendChild($d->createElement('body'));
@ -84,6 +202,17 @@ class TestHTMLElement extends \PHPUnit\Framework\TestCase {
}
public function testProperty_inputMode(): void {
$d = new Document('<!DOCTYPE html><html></html>');
$html = $d->documentElement;
$this->assertSame('', $html->inputMode);
$html->inputMode = 'ook';
$this->assertSame('', $html->inputMode);
$html->inputMode = 'tel';
$this->assertSame('tel', $html->inputMode);
}
/**
* @covers \MensBeam\HTML\DOM\HTMLElement::__set_outerText
*
@ -102,7 +231,7 @@ class TestHTMLElement extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\HTML\DOM\Inner\NodeCache::set
* @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor
*/
public function testProperty_outerText__errors() {
public function testProperty_outerText__errors(): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode(DOMException::NO_MODIFICATION_ALLOWED);
$d = new Document();

5
vendor-bin/phpunit/composer.lock

@ -92,9 +92,6 @@
"require": {
"php": "^7.1 || ^8.0"
},
"replace": {
"myclabs/deep-copy": "self.version"
},
"require-dev": {
"doctrine/collections": "^1.0",
"doctrine/common": "^2.6",
@ -2108,5 +2105,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.1.0"
"plugin-api-version": "2.2.0"
}

2
vendor-bin/robo/composer.lock

@ -2012,5 +2012,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.1.0"
"plugin-api-version": "2.2.0"
}

Loading…
Cancel
Save