Browse Source

Added DOMTokenList, added back in tests

wrapper-classes
Dustin Wilson 2 years ago
parent
commit
4ab67fbdad
  1. 95
      composer.lock
  2. 331
      lib/DOMTokenList.php
  3. 42
      lib/Element.php
  4. 224
      tests/cases/TestDOMTokenList.php
  5. 70
      tests/cases/TestElement.php
  6. 1
      tests/phpunit.dist.xml
  7. 1
      vendor-bin/phpunit/composer.lock
  8. 84
      vendor-bin/robo/composer.lock

95
composer.lock

@ -1440,16 +1440,16 @@
},
{
"name": "symfony/console",
"version": "v5.3.10",
"version": "v5.3.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3"
"reference": "3e7ab8f5905058984899b05a4648096f558bfeba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3",
"reference": "d4e409d9fbcfbf71af0e5a940abb7b0b4bad0bd3",
"url": "https://api.github.com/repos/symfony/console/zipball/3e7ab8f5905058984899b05a4648096f558bfeba",
"reference": "3e7ab8f5905058984899b05a4648096f558bfeba",
"shasum": ""
},
"require": {
@ -1462,7 +1462,6 @@
"symfony/string": "^5.1"
},
"conflict": {
"psr/log": ">=3",
"symfony/dependency-injection": "<4.4",
"symfony/dotenv": "<5.1",
"symfony/event-dispatcher": "<4.4",
@ -1519,7 +1518,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.3.10"
"source": "https://github.com/symfony/console/tree/v5.3.11"
},
"funding": [
{
@ -1535,20 +1534,20 @@
"type": "tidelift"
}
],
"time": "2021-10-26T09:30:15+00:00"
"time": "2021-11-21T19:41:05+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.4.0",
"version": "v2.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
"reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8",
"reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8",
"shasum": ""
},
"require": {
@ -1557,7 +1556,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
"dev-main": "2.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1586,7 +1585,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0"
},
"funding": [
{
@ -1602,20 +1601,20 @@
"type": "tidelift"
}
],
"time": "2021-03-23T23:28:01+00:00"
"time": "2021-07-12T14:48:14+00:00"
},
{
"name": "symfony/http-foundation",
"version": "v5.3.10",
"version": "v5.3.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "9f34f02e8a5fdc7a56bafe011cea1ce97300e54c"
"reference": "d1e7059ebeb0b8f9fe5eb5b26eacd2e3c1f371cc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/9f34f02e8a5fdc7a56bafe011cea1ce97300e54c",
"reference": "9f34f02e8a5fdc7a56bafe011cea1ce97300e54c",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/d1e7059ebeb0b8f9fe5eb5b26eacd2e3c1f371cc",
"reference": "d1e7059ebeb0b8f9fe5eb5b26eacd2e3c1f371cc",
"shasum": ""
},
"require": {
@ -1659,7 +1658,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v5.3.10"
"source": "https://github.com/symfony/http-foundation/tree/v5.3.11"
},
"funding": [
{
@ -1675,20 +1674,20 @@
"type": "tidelift"
}
],
"time": "2021-10-11T15:41:55+00:00"
"time": "2021-11-04T16:37:19+00:00"
},
{
"name": "symfony/mime",
"version": "v5.3.8",
"version": "v5.3.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
"reference": "a756033d0a7e53db389618653ae991eba5a19a11"
"reference": "dffc0684f10526db12c52fcd6238c64695426d61"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/a756033d0a7e53db389618653ae991eba5a19a11",
"reference": "a756033d0a7e53db389618653ae991eba5a19a11",
"url": "https://api.github.com/repos/symfony/mime/zipball/dffc0684f10526db12c52fcd6238c64695426d61",
"reference": "dffc0684f10526db12c52fcd6238c64695426d61",
"shasum": ""
},
"require": {
@ -1742,7 +1741,7 @@
"mime-type"
],
"support": {
"source": "https://github.com/symfony/mime/tree/v5.3.8"
"source": "https://github.com/symfony/mime/tree/v5.3.11"
},
"funding": [
{
@ -1758,7 +1757,7 @@
"type": "tidelift"
}
],
"time": "2021-09-10T12:30:38+00:00"
"time": "2021-11-20T16:42:42+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -2415,16 +2414,16 @@
},
{
"name": "symfony/process",
"version": "v5.3.7",
"version": "v5.3.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "38f26c7d6ed535217ea393e05634cb0b244a1967"
"reference": "6c99204de85d04ca17f16c466fc61896960b0636"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/38f26c7d6ed535217ea393e05634cb0b244a1967",
"reference": "38f26c7d6ed535217ea393e05634cb0b244a1967",
"url": "https://api.github.com/repos/symfony/process/zipball/6c99204de85d04ca17f16c466fc61896960b0636",
"reference": "6c99204de85d04ca17f16c466fc61896960b0636",
"shasum": ""
},
"require": {
@ -2457,7 +2456,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v5.3.7"
"source": "https://github.com/symfony/process/tree/v5.3.11"
},
"funding": [
{
@ -2473,25 +2472,29 @@
"type": "tidelift"
}
],
"time": "2021-08-04T21:20:46+00:00"
"time": "2021-11-17T12:16:12+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v2.4.0",
"version": "v2.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb"
"reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
"reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc",
"reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.1"
"psr/container": "^1.1",
"symfony/deprecation-contracts": "^2.1"
},
"conflict": {
"ext-psr": "<1.1|>=2"
},
"suggest": {
"symfony/service-implementation": ""
@ -2499,7 +2502,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
"dev-main": "2.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -2536,7 +2539,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v2.4.0"
"source": "https://github.com/symfony/service-contracts/tree/v2.5.0"
},
"funding": [
{
@ -2552,7 +2555,7 @@
"type": "tidelift"
}
],
"time": "2021-04-01T10:43:52+00:00"
"time": "2021-11-04T16:48:04+00:00"
},
{
"name": "symfony/string",
@ -2639,16 +2642,16 @@
},
{
"name": "symfony/yaml",
"version": "v5.3.6",
"version": "v5.3.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7"
"reference": "226638aa877bc4104e619a15f27d8141cd6b4e4a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7",
"reference": "4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7",
"url": "https://api.github.com/repos/symfony/yaml/zipball/226638aa877bc4104e619a15f27d8141cd6b4e4a",
"reference": "226638aa877bc4104e619a15f27d8141cd6b4e4a",
"shasum": ""
},
"require": {
@ -2694,7 +2697,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v5.3.6"
"source": "https://github.com/symfony/yaml/tree/v5.3.11"
},
"funding": [
{
@ -2710,7 +2713,7 @@
"type": "tidelift"
}
],
"time": "2021-07-29T06:20:01+00:00"
"time": "2021-11-20T16:42:42+00:00"
},
{
"name": "webuni/front-matter",

331
lib/DOMTokenList.php

@ -0,0 +1,331 @@
<?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;
use MensBeam\Framework\MagicProperties,
MensBeam\HTML\DOM\Inner\Reflection;
class DOMTokenList implements \ArrayAccess, \Countable, \Iterator {
use MagicProperties;
protected string $localName;
protected \WeakReference $element;
protected int $_length = 0;
protected int $position = 0;
# A DOMTokenList object has an associated token set (a set), which is initially
# empty.
protected array $tokenSet = [];
private const ASCII_WHITESPACE_REGEX = '/[\t\n\x0c\r ]+/';
protected function __get_length(): int {
return $this->_length;
}
protected function __get_value(): string {
# The value attribute must return the result of running this’s serialize steps.
return $this->__toString();
}
protected function __set_value(string $value) {
# Setting the value attribute must set an attribute value for the associated
# element using associated attribute’s local name and the given value.
$element = Reflection::getProtectedProperty($this->element->get(), 'innerNode');
$element->setAttribute($this->localName, $value);
// Also update the token set and the length.
$this->tokenSet = $this->parseOrderedSet($value);
$this->_length = count($this->tokenSet);
}
protected function __construct(Element $element, string $attributeLocalName) {
# A DOMTokenList object also has an associated element and an attribute’s local
# name.
# When a DOMTokenList object is created, then:
#
# 1. Let element be associated element.
// Using a weak reference here to prevent a circular reference. We should be
// creating a weak reference to the inner element, but there are memory
// management issues concerning PHP's garbage reference counting.
$this->element = \WeakReference::create($element);
# 2. Let localName be associated attribute’s local name.
$this->localName = $attributeLocalName;
# 3. Let value be the result of getting an attribute value given element and
# localName.
$element = Reflection::getProtectedProperty($element, 'innerNode');
$value = $element->getAttribute($attributeLocalName);
# 4. Run the attribute change steps for element, localName, value, value, and
# null.
$this->attributeChange($attributeLocalName, $value, $value);
}
public function add(...$tokens): void {
# 1. For each token in tokens:
foreach ($tokens as $token) {
# 1. If token is the empty string, then throw a "SyntaxError" DOMException.
if ($token === '') {
throw new DOMException(DOMException::SYNTAX_ERROR);
}
# 2. If token contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException.
if (preg_match(self::ASCII_WHITESPACE_REGEX, $token)) {
throw new DOMException(DOMException::INVALID_CHARACTER);
}
}
# 2. For each token in tokens, append token to this’s token set.
foreach ($tokens as $token) {
if (!in_array($token, $this->tokenSet)) {
$this->tokenSet[] = $token;
$this->_length++;
}
}
# 3. Run the update steps.
$this->update();
}
public function contains(string $token): bool {
return (in_array($token, $this->tokenSet));
}
public function count(): int {
return $this->_length;
}
public function current(): ?string {
return $this->item($this->position);
}
public function item(int $index): ?string {
# The item(index) method steps are:
# 1. If index is equal to or greater than this’s token set’s size, then return null.
if ($index >= $this->_length) {
return null;
}
# 2. Return this’s token set[index].
return $this->tokenSet[$index];
}
public function key(): int {
return $this->position;
}
public function next(): void {
++$this->position;
}
public function rewind(): void {
$this->position = 0;
}
public function offsetExists($offset): bool {
return isset($this->tokenSet[$offset]);
}
public function offsetGet($offset): string {
return $this->item($offset);
}
public function offsetSet($offset, $value): void {
// Spec says nothing about setting values on DOMTokenList outside of add();
// browsers silently fail here.
}
public function offsetUnset($offset): void {
// Spec says nothing about unsetting values on DOMTokenList outside of remove();
// browsers silently fail here.
}
public function remove(...$tokens): void {
# 1. For each token in tokens:
foreach ($tokens as $token) {
# 1. If token is the empty string, then throw a "SyntaxError" DOMException.
if ($token === '') {
throw new DOMException(DOMException::SYNTAX_ERROR);
}
# 2. If token contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException.
if (preg_match(self::ASCII_WHITESPACE_REGEX, $token)) {
throw new DOMException(DOMException::INVALID_CHARACTER);
}
}
# For each token in tokens, remove token from this’s token set.
$changed = false;
foreach ($tokens as $token) {
if ($key = array_search($token, $this->tokenSet, true)) {
unset($this->tokenSet[$key]);
$this->_length--;
$changed = true;
}
}
if ($changed) {
$this->tokenSet = array_values($this->tokenSet);
}
# 3. Run the update steps.
$this->update();
}
public function replace(string $token, string $newToken): bool {
# 1. If either token or newToken is the empty string, then throw a "SyntaxError"
# DOMException.
if ($token === '' || $newToken === '') {
throw new DOMException(DOMException::SYNTAX_ERROR);
}
# 2. If either token or newToken contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException.
if (preg_match(self::ASCII_WHITESPACE_REGEX, $token) || preg_match(self::ASCII_WHITESPACE_REGEX, $newToken)) {
throw new DOMException(DOMException::INVALID_CHARACTER);
}
# 3. If this’s token set does not contain token, then return false.
if (!($key = array_search($token, $this->tokenSet))) {
return false;
}
# 4. Replace token in this’s token set with newToken.
$this->tokenSet[$key] = $newToken;
# 5. Run the update steps.
$this->update();
# 6. Return true.
return true;
}
public function supports(string $token): bool {
# 1. Let result be the return value of validation steps called with token.
# 2. Return result.
#
# A DOMTokenList object’s validation steps for a given token are:
#
# 1. If the associated attribute’s local name does not define supported tokens,
# throw a TypeError.
# 2. Let lowercase token be a copy of token, in ASCII lowercase.
# 3. If lowercase token is present in supported tokens, return true.
# 4. Return false.
// This class is presently only used for Element::classList, and it supports any
// valid class name as a token. So, there's nothing to do here at the moment.
// Just return true.
return true;
}
public function toggle(string $token, ?bool $force = null): bool {
# 1. If token is the empty string, then throw a "SyntaxError" DOMException.
if ($token === '') {
throw new DOMException(DOMException::SYNTAX_ERROR);
}
# 2. If token contains any ASCII whitespace, then throw an
# "InvalidCharacterError" DOMException.
if (preg_match(self::ASCII_WHITESPACE_REGEX, $token)) {
throw new DOMException(DOMException::INVALID_CHARACTER);
}
# 3. If this’s token set[token] exists, then:
if (in_array($token, $this->tokenSet)) {
# 1. If force is either not given or is false, then remove token from this’s
# token set, run the update steps and return false.
if (!$force) {
$this->remove($token);
return false;
}
# 2. Return true.
return true;
}
# 4. Otherwise, if force not given or is true, append token to this’s token set,
# run the update steps, and return true.
elseif ($force === null || $force === true) {
$this->add($token);
return true;
}
# 5. Return false.
return false;
}
public function valid(): bool {
return array_key_exists($this->position, $this->tokenSet);
}
protected function attributeChange(string $localName, ?string $oldValue = null, ?string $value = null, ?string $namespace = null): void {
# A DOMTokenList object has these attribute change steps for its associated
# element:
#
# 1. If localName is associated attribute’s local name, namespace is null, and
# value is null, then empty token set.
if ($localName === $this->localName && $namespace === null && $value === null) {
$this->tokenSet = [];
$this->_length = 0;
}
# 2. Otherwise, if localName is associated attribute’s local name, namespace is
# null, then set token set to value, parsed.
elseif ($localName === $this->localName && $namespace === null) {
$this->tokenSet = $this->parseOrderedSet($value);
$this->_length = count($this->tokenSet);
}
}
protected function parseOrderedSet(string $input): array {
if ($input === '') {
return [];
}
# The ordered set parser takes a string input and then runs these steps:
#
# 1. Let inputTokens be the result of splitting input on ASCII whitespace.
// There isn't a Set object in php, so make sure all the tokens are unique.
$inputTokens = array_unique(preg_split(self::ASCII_WHITESPACE_REGEX, $input));
# 2. Let tokens be a new ordered set.
# 3. For each token in inputTokens, append token to tokens.
# 4. Return tokens.
// There isn't a Set object in php, so just return the uniqued input tokens.
return $inputTokens;
}
protected function update(): void {
# A DOMTokenList object’s update steps are:
#
# 1. If the associated element does not have an associated attribute and token
# set is empty, then return.
$element = Reflection::getProtectedProperty($this->element->get(), 'innerNode');
if (!$element->hasAttribute('class') && count($this->tokenSet) === 0) {
return;
}
# 2. Set an attribute value for the associated element using associated
# attribute’s local name and the result of running the ordered set serializer
# for token set.
$class = $element->ownerDocument->createAttribute($this->localName);
$class->value = $this->__toString();
$element->setAttributeNode($class);
}
public function __toString(): string {
# The ordered set serializer takes a set and returns the concatenation of set
# using U+0020 SPACE.
return implode(' ', $this->tokenSet);
}
}

42
lib/Element.php

@ -22,6 +22,15 @@ class Element extends Node {
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\NamedNodeMap', $this, ($this instanceof Document) ? $this->innerNode : $this->innerNode->ownerDocument, $this->innerNode->attributes);
}
protected function __get_classList(): DOMTokenList {
# The classList getter steps are to return a DOMTokenList object whose
# associated element is this and whose associated attribute’s local name is
# class. The token set of this particular DOMTokenList object are also known as
# the element’s classes.
// DOMTokenLists cannot be created from their constructors normally.
return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\DOMTokenList', $this, 'class');
}
protected function __get_className(): string {
# The className attribute must reflect the "class" content attribute.
# Return the result of running get an attribute value given this and name.
@ -339,39 +348,6 @@ class Element extends Node {
$this->innerNode->setAttributeNS($namespace, $qualifiedName, $value);
}
/*# 2. Set an attribute value for this using localName, value, and also prefix and
# namespace.
// Going to try to handle this by getting the PHP DOM to do the heavy lifting
// when we can because it's faster.
// NOTE: We create attribute nodes so that xmlns attributes don't get lost;
// otherwise they cannot be serialized
if ($namespace === self::XMLNS_NAMESPACE) {
// Xmlns attributes have special bugs just for them. How lucky! Xmlns attribute
// nodes won't stick and can actually cause segmentation faults if created on a
// no longer existing document element, appended to another element, and then
// retrieved. So, use the methods used in Document::createAttributeNS to get an
// attribute node.
$a = $this->ownerDocument->createAttributeNS($namespace, $qualifiedName);
$a->value = $this->escapeString($value, true);
$this->setAttributeNodeNS($a);
} else {
try {
$this->innerNode->setAttributeNS($namespace, $qualifiedName, $value);
} catch (\DOMException $e) {
// The attribute name is invalid for XML
// Replace any offending characters with "UHHHHHH" where H are the
// uppercase hexadecimal digits of the character's code point
if ($prefix !== null) {
$qualifiedName = $this->coerceName($prefix) . ':' . $this->coerceName($localName);
} else {
$qualifiedName = $this->coerceName($qualifiedName);
}
$this->innerNode->setAttributeNS($namespace, $qualifiedName, $value);
}
}*/
// If you create an id attribute this way it won't be used by PHP in
// getElementById, so let's fix that.
if ($namespace === null && $qualifiedName === 'id') {

224
tests/cases/TestDOMTokenList.php

@ -0,0 +1,224 @@
<?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,
Text,
XMLDocument
};
/** @covers \MensBeam\HTML\DOM\DOMTokenList */
class TestDOMTokenList extends \PHPUnit\Framework\TestCase {
public function provideMethod_add_remove_replace_toggle__errors(): iterable {
return [
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->add('');
}, DOMException::SYNTAX_ERROR ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->remove('');
}, DOMException::SYNTAX_ERROR ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->replace('ack', '');
}, DOMException::SYNTAX_ERROR ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->toggle('');
}, DOMException::SYNTAX_ERROR ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->add('fail fail');
}, DOMException::INVALID_CHARACTER ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->remove('fail fail');
}, DOMException::INVALID_CHARACTER ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->replace('ack', 'fail fail');
}, DOMException::INVALID_CHARACTER ],
[ function() {
$d = new Document();
$e = $d->createElement('html');
$e->classList->toggle('fail fail');
}, DOMException::INVALID_CHARACTER ]
];
}
/** @dataProvider provideMethod_add_remove_replace_toggle__errors */
public function testMethod_add_remove_replace_toggle__errors(\Closure $closure, int $errorCode): void {
$this->expectException(DOMException::class);
$this->expectExceptionCode($errorCode);
$closure();
}
/**
* @covers \MensBeam\HTML\DOM\DOMTokenList::contains
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::createElement
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__toString
* @covers \MensBeam\HTML\DOM\DOMTokenList::add
* @covers \MensBeam\HTML\DOM\DOMTokenList::attributeChange
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\DOMTokenList::update
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Element::__get_classList
* @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_contains() {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertTrue($e->classList->contains('ack'));
$this->assertFalse($e->classList->contains('fail'));
}
public function testMethod_count(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertSame(4, count($e->classList));
}
public function testMethod_item(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertNull($e->classList->item(42));
}
public function testMethod_replace(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->setAttribute('class', 'ook eek ack ookeek');
$this->assertTrue($e->classList->replace('ack', 'what'));
$this->assertSame('ook eek what ookeek', $e->classList->value);
$this->assertSame('ook eek what ookeek', $e->getAttribute('class'));
$this->assertFalse($e->classList->replace('fail', 'eekook'));
}
public function testMethod_remove(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->setAttribute('class', 'ook eek ack ookeek');
$e->classList->remove('ack');
$this->assertSame('ook eek ookeek', $e->classList->value);
$this->assertSame('ook eek ookeek', $e->getAttribute('class'));
}
public function testMethod_supports(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertTrue($e->classList->supports('ack'));
}
public function testMethod_toggle(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->setAttribute('class', 'ook eek ack ookeek');
$this->assertFalse($e->classList->toggle('ack'));
$this->assertSame('ook eek ookeek', $e->classList->value);
$this->assertSame('ook eek ookeek', $e->getAttribute('class'));
$this->assertTrue($e->classList->toggle('ack'));
$this->assertSame('ook eek ookeek ack', $e->classList->value);
$this->assertSame('ook eek ookeek ack', $e->getAttribute('class'));
$this->assertTrue($e->classList->toggle('ack', true));
$this->assertSame('ook eek ookeek ack', $e->classList->value);
$this->assertSame('ook eek ookeek ack', $e->getAttribute('class'));
$this->assertFalse($e->classList->toggle('eekook', false));
$this->assertSame('ook eek ookeek ack', $e->classList->value);
$this->assertSame('ook eek ookeek ack', $e->getAttribute('class'));
$this->assertTrue($e->classList->toggle('eekook', true));
$this->assertSame('ook eek ookeek ack eekook', $e->classList->value);
$this->assertSame('ook eek ookeek ack eekook', $e->getAttribute('class'));
}
public function testProcess_iteration(): void {
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
foreach ($e->classList as $key => $className) {
$this->assertSame($className, $e->classList[$key]);
// test offsetExists
$this->assertTrue(isset($e->classList[$key]));
}
}
public function testProperty_value(): void {
// Test it with and without an attached document element
$d = new Document();
$e = $d->createElement('html');
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertSame('ook eek ack ookeek', $e->classList->value);
$this->assertSame('ook eek ack ookeek', $e->getAttribute('class'));
$e->classList->value = 'omg wtf bbq lol zor bor xxx';
$this->assertSame('lol', $e->classList[3]);
$this->assertSame('omg wtf bbq lol zor bor xxx', $e->classList->value);
$this->assertSame('omg wtf bbq lol zor bor xxx', $e->getAttribute('class'));
$e->classList->value = '';
$this->assertSame('', $e->classList->value);
$this->assertSame('', $e->getAttribute('class'));
$d = new Document();
$e = $d->appendChild($d->createElement('html'));
$e->classList->add('ook', 'eek', 'ack', 'ookeek');
$this->assertSame('ook eek ack ookeek', $e->classList->value);
$this->assertSame('ook eek ack ookeek', $e->getAttribute('class'));
$e->classList->value = 'omg wtf bbq lol zor bor xxx';
$this->assertSame('lol', $e->classList[3]);
$this->assertSame('omg wtf bbq lol zor bor xxx', $e->classList->value);
$this->assertSame('omg wtf bbq lol zor bor xxx', $e->getAttribute('class'));
$e->classList->value = '';
$this->assertSame('', $e->classList->value);
$this->assertSame('', $e->getAttribute('class'));
}
}

70
tests/cases/TestElement.php

@ -514,6 +514,76 @@ class TestElement extends \PHPUnit\Framework\TestCase {
}
/**
* @covers \MensBeam\HTML\DOM\Element::__get_classList
*
* @covers \MensBeam\HTML\DOM\Document::__construct
* @covers \MensBeam\HTML\DOM\Document::__get_documentElement
* @covers \MensBeam\HTML\DOM\Document::load
* @covers \MensBeam\HTML\DOM\DOMImplementation::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__construct
* @covers \MensBeam\HTML\DOM\DOMTokenList::__get_length
* @covers \MensBeam\HTML\DOM\DOMTokenList::attributeChange
* @covers \MensBeam\HTML\DOM\DOMTokenList::parseOrderedSet
* @covers \MensBeam\HTML\DOM\Element::__construct
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::hasChildNodes
* @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 testProperty_classList() {
$d = new Document('<!DOCTYPE html><html class="ook eek ack foo bar"></html>', 'UTF-8');
$documentElement = $d->documentElement;
$this->assertEquals(5, $documentElement->classList->length);
}
/**
* @covers \MensBeam\HTML\DOM\Element::__get_className
* @covers \MensBeam\HTML\DOM\Element::__set_className
*
* @covers \MensBeam\HTML\DOM\Attr::__get_value
* @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\Element::__get_namespaceURI
* @covers \MensBeam\HTML\DOM\Element::getAttribute
* @covers \MensBeam\HTML\DOM\Element::getAttributeNode
* @covers \MensBeam\HTML\DOM\Element::setAttribute
* @covers \MensBeam\HTML\DOM\Node::__construct
* @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument
* @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::__get_wrapperNode
* @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 testProperty_className() {
$d = new Document();
$documentElement = $d->appendChild($d->createElement('html'));
$documentElement->className = 'ook';
$this->assertSame('ook', $documentElement->className);
}
/**
* @covers \MensBeam\HTML\DOM\Element::__get_id
* @covers \MensBeam\HTML\DOM\Element::__set_id

1
tests/phpunit.dist.xml

@ -18,6 +18,7 @@
<testsuite name="DOM">
<file>cases/TestDocument.php</file>
<file>cases/TestDocumentOrElement.php</file>
<file>cases/TestDOMTokenList.php</file>
<file>cases/TestElement.php</file>
<file>cases/TestNode.php</file>
</testsuite>

1
vendor-bin/phpunit/composer.lock

@ -1801,6 +1801,7 @@
"type": "github"
}
],
"abandoned": true,
"time": "2020-09-28T06:45:17+00:00"
},
{

84
vendor-bin/robo/composer.lock

@ -855,16 +855,16 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.4.0",
"version": "v2.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
"reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8",
"reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8",
"shasum": ""
},
"require": {
@ -873,7 +873,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
"dev-main": "2.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -902,7 +902,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0"
},
"funding": [
{
@ -918,20 +918,20 @@
"type": "tidelift"
}
],
"time": "2021-03-23T23:28:01+00:00"
"time": "2021-07-12T14:48:14+00:00"
},
{
"name": "symfony/event-dispatcher",
"version": "v5.3.7",
"version": "v5.3.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "ce7b20d69c66a20939d8952b617506a44d102130"
"reference": "661a7a6e085394f8513945669e31f7c1338a7e69"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ce7b20d69c66a20939d8952b617506a44d102130",
"reference": "ce7b20d69c66a20939d8952b617506a44d102130",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/661a7a6e085394f8513945669e31f7c1338a7e69",
"reference": "661a7a6e085394f8513945669e31f7c1338a7e69",
"shasum": ""
},
"require": {
@ -987,7 +987,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v5.3.7"
"source": "https://github.com/symfony/event-dispatcher/tree/v5.3.11"
},
"funding": [
{
@ -1003,20 +1003,20 @@
"type": "tidelift"
}
],
"time": "2021-08-04T21:20:46+00:00"
"time": "2021-11-17T12:16:12+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v2.4.0",
"version": "v2.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11"
"reference": "66bea3b09be61613cd3b4043a65a8ec48cfa6d2a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11",
"reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/66bea3b09be61613cd3b4043a65a8ec48cfa6d2a",
"reference": "66bea3b09be61613cd3b4043a65a8ec48cfa6d2a",
"shasum": ""
},
"require": {
@ -1029,7 +1029,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
"dev-main": "2.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1066,7 +1066,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0"
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.0"
},
"funding": [
{
@ -1082,7 +1082,7 @@
"type": "tidelift"
}
],
"time": "2021-03-23T23:28:01+00:00"
"time": "2021-07-12T14:48:14+00:00"
},
{
"name": "symfony/filesystem",
@ -1697,16 +1697,16 @@
},
{
"name": "symfony/process",
"version": "v5.3.7",
"version": "v5.3.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "38f26c7d6ed535217ea393e05634cb0b244a1967"
"reference": "6c99204de85d04ca17f16c466fc61896960b0636"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/38f26c7d6ed535217ea393e05634cb0b244a1967",
"reference": "38f26c7d6ed535217ea393e05634cb0b244a1967",
"url": "https://api.github.com/repos/symfony/process/zipball/6c99204de85d04ca17f16c466fc61896960b0636",
"reference": "6c99204de85d04ca17f16c466fc61896960b0636",
"shasum": ""
},
"require": {
@ -1739,7 +1739,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v5.3.7"
"source": "https://github.com/symfony/process/tree/v5.3.11"
},
"funding": [
{
@ -1755,25 +1755,29 @@
"type": "tidelift"
}
],
"time": "2021-08-04T21:20:46+00:00"
"time": "2021-11-17T12:16:12+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v2.4.0",
"version": "v2.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb"
"reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
"reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc",
"reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.1"
"psr/container": "^1.1",
"symfony/deprecation-contracts": "^2.1"
},
"conflict": {
"ext-psr": "<1.1|>=2"
},
"suggest": {
"symfony/service-implementation": ""
@ -1781,7 +1785,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
"dev-main": "2.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1818,7 +1822,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v2.4.0"
"source": "https://github.com/symfony/service-contracts/tree/v2.5.0"
},
"funding": [
{
@ -1834,7 +1838,7 @@
"type": "tidelift"
}
],
"time": "2021-04-01T10:43:52+00:00"
"time": "2021-11-04T16:48:04+00:00"
},
{
"name": "symfony/string",
@ -1921,16 +1925,16 @@
},
{
"name": "symfony/yaml",
"version": "v5.3.6",
"version": "v5.3.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7"
"reference": "226638aa877bc4104e619a15f27d8141cd6b4e4a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7",
"reference": "4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7",
"url": "https://api.github.com/repos/symfony/yaml/zipball/226638aa877bc4104e619a15f27d8141cd6b4e4a",
"reference": "226638aa877bc4104e619a15f27d8141cd6b4e4a",
"shasum": ""
},
"require": {
@ -1976,7 +1980,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v5.3.6"
"source": "https://github.com/symfony/yaml/tree/v5.3.11"
},
"funding": [
{
@ -1992,7 +1996,7 @@
"type": "tidelift"
}
],
"time": "2021-07-29T06:20:01+00:00"
"time": "2021-11-20T16:42:42+00:00"
}
],
"packages-dev": [],

Loading…
Cancel
Save