From 6f41dd238987c6650f93f3507d40e4de6a8e7c85 Mon Sep 17 00:00:00 2001 From: Thiemo Kreuz Date: Mon, 16 Apr 2018 14:22:51 +0200 Subject: [PATCH] Update to MediaWiki CodeSniffer version 18 --- .phpcs.xml | 19 ++ composer.json | 2 +- phpcs.xml | 21 -- src/AttributeMatcher.php | 129 ++++----- src/ChildElementMatcher.php | 98 +++---- src/ClassMatcher.php | 80 +++--- src/ComplexTagMatcher.php | 382 ++++++++++++------------- src/DirectChildElementMatcher.php | 128 ++++----- src/HtmlMatcher.php | 185 ++++++------ src/RootElementMatcher.php | 106 ++++--- src/TagMatcher.php | 10 +- src/TagNameMatcher.php | 68 +++-- src/TextContentsMatcher.php | 62 ++-- src/XmlNodeRecursiveIterator.php | 45 ++- src/functions.php | 134 ++++----- tests/ComplexTagMatcherTest.php | 257 +++++++++-------- tests/FunctionsTest.php | 366 ++++++++++++----------- tests/HtmlMatcherTest.php | 152 +++++----- tests/XmlNodeRecursiveIteratorTest.php | 99 +++---- tests/bootstrap.php | 4 +- 20 files changed, 1137 insertions(+), 1210 deletions(-) create mode 100644 .phpcs.xml delete mode 100644 phpcs.xml diff --git a/.phpcs.xml b/.phpcs.xml new file mode 100644 index 0000000..4c644f2 --- /dev/null +++ b/.phpcs.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + . + + + diff --git a/composer.json b/composer.json index b363d23..1135678 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "hamcrest/hamcrest-php": "^1.1.1||^2.0" }, "require-dev": { - "wikibase/wikibase-codesniffer": "^0.1.0", + "mediawiki/mediawiki-codesniffer": "18.0.0", "phpunit/phpunit": "^4.8" }, "autoload": { diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index 70b8d37..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - functions\.php - - - . - \ No newline at end of file diff --git a/src/AttributeMatcher.php b/src/AttributeMatcher.php index 5d4bd1c..5f4e719 100644 --- a/src/AttributeMatcher.php +++ b/src/AttributeMatcher.php @@ -6,82 +6,77 @@ use Hamcrest\Matcher; use Hamcrest\Util; -class AttributeMatcher extends TagMatcher -{ +class AttributeMatcher extends TagMatcher { - /** - * @var Matcher - */ - private $attributeNameMatcher; + /** + * @var Matcher + */ + private $attributeNameMatcher; - /** - * @var Matcher|null - */ - private $valueMatcher; + /** + * @var Matcher|null + */ + private $valueMatcher; - public static function withAttribute($attributeName) { - return new static(Util::wrapValueWithIsEqual($attributeName)); - } + public static function withAttribute( $attributeName ) { + return new static( Util::wrapValueWithIsEqual( $attributeName ) ); + } - /** - * AttributeMatcher constructor. - * @param \Hamcrest\Matcher $attributeNameMatcher - */ - public function __construct(Matcher $attributeNameMatcher) - { - parent::__construct(); + /** + * AttributeMatcher constructor. + * @param \Hamcrest\Matcher $attributeNameMatcher + */ + public function __construct( Matcher $attributeNameMatcher ) { + parent::__construct(); - $this->attributeNameMatcher = $attributeNameMatcher; - } + $this->attributeNameMatcher = $attributeNameMatcher; + } - /** - * @param Matcher|mixed $value - * @return AttributeMatcher - */ - public function havingValue($value) - { - //TODO: Throw exception if value is set - $result = clone $this; - $result->valueMatcher = Util::wrapValueWithIsEqual($value); + /** + * @param Matcher|mixed $value + * @return AttributeMatcher + */ + public function havingValue( $value ) { + // TODO: Throw exception if value is set + $result = clone $this; + $result->valueMatcher = Util::wrapValueWithIsEqual( $value ); - return $result; - } + return $result; + } - public function describeTo(Description $description) - { - $description->appendText('with attribute ') - ->appendDescriptionOf($this->attributeNameMatcher); - if ($this->valueMatcher) { - $description->appendText(' having value ') - ->appendDescriptionOf($this->valueMatcher); - } - } + public function describeTo( Description $description ) { + $description->appendText( 'with attribute ' ) + ->appendDescriptionOf( $this->attributeNameMatcher ); + if ( $this->valueMatcher ) { + $description->appendText( ' having value ' ) + ->appendDescriptionOf( $this->valueMatcher ); + } + } - /** - * @param \DOMElement $item - * @param Description $mismatchDescription - * - * @return bool - */ - protected function matchesSafelyWithDiagnosticDescription($item, Description $mismatchDescription) - { - /** @var \DOMAttr $attribute */ - foreach ($item->attributes as $attribute) { - if ($this->valueMatcher) { - if ( - $this->attributeNameMatcher->matches($attribute->name) - && $this->valueMatcher->matches($attribute->value) - ) { - return true; - } - } else { - if ($this->attributeNameMatcher->matches($attribute->name)) { - return true; - } - } - } + /** + * @param \DOMElement $item + * @param Description $mismatchDescription + * + * @return bool + */ + protected function matchesSafelyWithDiagnosticDescription( $item, Description $mismatchDescription ) { + /** @var \DOMAttr $attribute */ + foreach ( $item->attributes as $attribute ) { + if ( $this->valueMatcher ) { + if ( + $this->attributeNameMatcher->matches( $attribute->name ) + && $this->valueMatcher->matches( $attribute->value ) + ) { + return true; + } + } else { + if ( $this->attributeNameMatcher->matches( $attribute->name ) ) { + return true; + } + } + } - return false; - } + return false; + } } diff --git a/src/ChildElementMatcher.php b/src/ChildElementMatcher.php index a3d020d..9599f4d 100644 --- a/src/ChildElementMatcher.php +++ b/src/ChildElementMatcher.php @@ -6,66 +6,62 @@ use Hamcrest\Matcher; use Hamcrest\TypeSafeDiagnosingMatcher; -class ChildElementMatcher extends TypeSafeDiagnosingMatcher -{ +class ChildElementMatcher extends TypeSafeDiagnosingMatcher { - /** - * @var Matcher|null - */ - private $matcher; + /** + * @var Matcher|null + */ + private $matcher; - public static function havingChild(Matcher $elementMatcher = null) { - return new static($elementMatcher); - } + public static function havingChild( Matcher $elementMatcher = null ) { + return new static( $elementMatcher ); + } - public function __construct(Matcher $matcher = null) - { - parent::__construct(\DOMNode::class); - $this->matcher = $matcher; - } + public function __construct( Matcher $matcher = null ) { + parent::__construct( \DOMNode::class ); + $this->matcher = $matcher; + } - public function describeTo(Description $description) - { - $description->appendText('having child '); - if ($this->matcher) { - $description->appendDescriptionOf($this->matcher); - } - } + public function describeTo( Description $description ) { + $description->appendText( 'having child ' ); + if ( $this->matcher ) { + $description->appendDescriptionOf( $this->matcher ); + } + } - /** - * @param \DOMDocument|\DOMNode $item - * @param Description $mismatchDescription - * - * @return bool - */ - protected function matchesSafelyWithDiagnosticDescription($item, Description $mismatchDescription) - { - if ($item instanceof \DOMDocument) { - $directChildren = iterator_to_array($item->documentElement->childNodes); + /** + * @param \DOMDocument|\DOMNode $item + * @param Description $mismatchDescription + * + * @return bool + */ + protected function matchesSafelyWithDiagnosticDescription( $item, Description $mismatchDescription ) { + if ( $item instanceof \DOMDocument ) { + $directChildren = iterator_to_array( $item->documentElement->childNodes ); - $body = array_shift($directChildren); - $directChildren = $body->childNodes; - } else { - $directChildren = $item->childNodes; - } + $body = array_shift( $directChildren ); + $directChildren = $body->childNodes; + } else { + $directChildren = $item->childNodes; + } - if ($directChildren->length === 0) { - $mismatchDescription->appendText('having no children'); - return false; - } + if ( $directChildren->length === 0 ) { + $mismatchDescription->appendText( 'having no children' ); + return false; + } - if (!$this->matcher) { - return $directChildren->length > 0; - } + if ( !$this->matcher ) { + return $directChildren->length > 0; + } - foreach (new XmlNodeRecursiveIterator($directChildren) as $child) { - if ($this->matcher && $this->matcher->matches($child)) { - return true; - } - } + foreach ( new XmlNodeRecursiveIterator( $directChildren ) as $child ) { + if ( $this->matcher && $this->matcher->matches( $child ) ) { + return true; + } + } - $mismatchDescription->appendText('having no children ')->appendDescriptionOf($this->matcher); - return false; - } + $mismatchDescription->appendText( 'having no children ' )->appendDescriptionOf( $this->matcher ); + return false; + } } diff --git a/src/ClassMatcher.php b/src/ClassMatcher.php index c11372b..00b0dfe 100644 --- a/src/ClassMatcher.php +++ b/src/ClassMatcher.php @@ -6,47 +6,43 @@ use Hamcrest\Matcher; use Hamcrest\Util; -class ClassMatcher extends TagMatcher -{ - - /** - * @var Matcher - */ - private $classMatcher; - - public static function withClass($class) { - return new static(Util::wrapValueWithIsEqual($class)); - } - - public function __construct(Matcher $class) - { - parent::__construct(); - $this->classMatcher = $class; - } - - public function describeTo(Description $description) - { - $description->appendText('with class ')->appendDescriptionOf($this->classMatcher); - } - - /** - * @param \DOMElement $item - * @param Description $mismatchDescription - * - * @return bool - */ - protected function matchesSafelyWithDiagnosticDescription($item, Description $mismatchDescription) - { - $classAttribute = $item->getAttribute('class'); - - $classes = preg_split('/\s+/u', $classAttribute); - foreach ($classes as $class) { - if ($this->classMatcher->matches($class)) { - return true; - } - } - - return false; - } +class ClassMatcher extends TagMatcher { + + /** + * @var Matcher + */ + private $classMatcher; + + public static function withClass( $class ) { + return new static( Util::wrapValueWithIsEqual( $class ) ); + } + + public function __construct( Matcher $class ) { + parent::__construct(); + $this->classMatcher = $class; + } + + public function describeTo( Description $description ) { + $description->appendText( 'with class ' )->appendDescriptionOf( $this->classMatcher ); + } + + /** + * @param \DOMElement $item + * @param Description $mismatchDescription + * + * @return bool + */ + protected function matchesSafelyWithDiagnosticDescription( $item, Description $mismatchDescription ) { + $classAttribute = $item->getAttribute( 'class' ); + + $classes = preg_split( '/\s+/u', $classAttribute ); + foreach ( $classes as $class ) { + if ( $this->classMatcher->matches( $class ) ) { + return true; + } + } + + return false; + } } diff --git a/src/ComplexTagMatcher.php b/src/ComplexTagMatcher.php index f95b7cf..758de40 100644 --- a/src/ComplexTagMatcher.php +++ b/src/ComplexTagMatcher.php @@ -8,203 +8,189 @@ use Hamcrest\Description; use Hamcrest\Matcher; -class ComplexTagMatcher extends TagMatcher -{ - - /** - * @link http://www.xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors - * @link https://github.com/Chronic-Dev/libxml2/blob/683f296a905710ff285c28b8644ef3a3d8be9486/include/libxml/xmlerror.h#L257 - */ - const XML_UNKNOWN_TAG_ERROR_CODE = 801; - - /** - * @var string - */ - private $tagHtmlOutline; - - /** - * @var Matcher - */ - private $matcher; - - /** - * @param string $htmlOutline - * @return self - */ - public static function tagMatchingOutline($htmlOutline) - { - return new self($htmlOutline); - } - - public function __construct($tagHtmlRepresentation) - { - parent::__construct(); - - $this->tagHtmlOutline = $tagHtmlRepresentation; - $this->matcher = $this->createMatcherFromHtml($tagHtmlRepresentation); - } - - public function describeTo(Description $description) - { - $description->appendText('tag matching outline `') - ->appendText($this->tagHtmlOutline) - ->appendText('` '); - } - - /** - * @param \DOMElement $item - * @param Description $mismatchDescription - * - * @return bool - */ - protected function matchesSafelyWithDiagnosticDescription($item, Description $mismatchDescription) - { - $result = $this->matcher->matches($item); - if (!$result) { - $mismatchDescription->appendText('was `') - ->appendText($this->elementToString($item)) - ->appendText('`'); - } - return $result; - } - - private function createMatcherFromHtml($htmlOutline) - { - $document = $this->parseHtml($htmlOutline); - $targetTag = $this->getSingleTagFromThe($document); - - $this->assertTagDoesNotContainChildren($targetTag); - - $attributeMatchers = $this->createAttributeMatchers($htmlOutline, $targetTag); - $classMatchers = $this->createClassMatchers($targetTag); - - return AllOf::allOf( - new TagNameMatcher(IsEqual::equalTo($targetTag->tagName)), - call_user_func_array([AllOf::class, 'allOf'], $attributeMatchers), - call_user_func_array([AllOf::class, 'allOf'], $classMatchers) - ); - } - - private function isUnknownTagError(\LibXMLError $error) - { - return $error->code === self::XML_UNKNOWN_TAG_ERROR_CODE; - } - - private function isBooleanAttribute($inputHtml, $attributeName) - { - $quotedName = preg_quote($attributeName, '/'); - - $attributeHasValueAssigned = preg_match("/\b{$quotedName}\s*=/ui", $inputHtml); - return !$attributeHasValueAssigned; - } - - /** - * @param $html - * - * @return \DOMDocument - * @throws \InvalidArgumentException - */ - private function parseHtml($html) - { - $internalErrors = libxml_use_internal_errors(true); - $document = new \DOMDocument(); - - if (!@$document->loadHTML($html)) { - throw new \InvalidArgumentException("There was some parsing error of `$html`"); - } - - $errors = libxml_get_errors(); - libxml_clear_errors(); - libxml_use_internal_errors($internalErrors); - - /** @var \LibXMLError $error */ - foreach ($errors as $error) { - if ($this->isUnknownTagError($error)) { - continue; - } - - throw new \InvalidArgumentException( - 'There was parsing error: ' . trim($error->message) . ' on line ' . $error->line - ); - } - - return $document; - } - - /** - * @param \DOMDocument $document - * - * @return \DOMElement - * @throws \InvalidArgumentException - */ - private function getSingleTagFromThe(\DOMDocument $document) - { - $directChildren = iterator_to_array($document->documentElement->childNodes); - - $body = array_shift($directChildren); - $directChildren = iterator_to_array($body->childNodes); - - if (count($directChildren) !== 1) { - throw new InvalidArgumentException('Expected exactly 1 tag description, got ' . count($directChildren)); - } - - return $directChildren[0]; - } - - private function assertTagDoesNotContainChildren(\DOMElement $targetTag) - { - if ($targetTag->childNodes->length > 0) { - throw new InvalidArgumentException('Nested elements are not allowed'); - } - } - - /** - * @param string $inputHtml - * @param $targetTag - * @return AttributeMatcher[] - */ - private function createAttributeMatchers($inputHtml, \DOMElement $targetTag) - { - $attributeMatchers = []; - /** @var \DOMAttr $attribute */ - foreach ($targetTag->attributes as $attribute) { - if ($attribute->name === 'class') { - continue; - } - - $attributeMatcher = new AttributeMatcher(IsEqual::equalTo($attribute->name)); - if (!$this->isBooleanAttribute($inputHtml, $attribute->name)) { - $attributeMatcher = $attributeMatcher->havingValue(IsEqual::equalTo($attribute->value)); - } - - $attributeMatchers[] = $attributeMatcher; - } - return $attributeMatchers; - } - - /** - * @param \DOMElement $targetTag - * @return ClassMatcher[] - */ - private function createClassMatchers($targetTag) - { - $classMatchers = []; - $classValue = $targetTag->getAttribute('class'); - foreach (explode(' ', $classValue) as $expectedClass) { - if ($expectedClass === '') { - continue; - } - $classMatchers[] = new ClassMatcher(IsEqual::equalTo($expectedClass)); - } - return $classMatchers; - } - - private function elementToString(\DOMElement $element) - { - $newDocument = new \DOMDocument(); - $cloned = $element->cloneNode(true); - $newDocument->appendChild($newDocument->importNode($cloned, true)); - return trim($newDocument->saveHTML()); - } +class ComplexTagMatcher extends TagMatcher { + + /** + * @link http://www.xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors + * @link https://github.com/Chronic-Dev/libxml2/blob/683f296a905710ff285c28b8644ef3a3d8be9486/include/libxml/xmlerror.h#L257 + */ + const XML_UNKNOWN_TAG_ERROR_CODE = 801; + + /** + * @var string + */ + private $tagHtmlOutline; + + /** + * @var Matcher + */ + private $matcher; + + /** + * @param string $htmlOutline + * @return self + */ + public static function tagMatchingOutline( $htmlOutline ) { + return new self( $htmlOutline ); + } + + public function __construct( $tagHtmlRepresentation ) { + parent::__construct(); + + $this->tagHtmlOutline = $tagHtmlRepresentation; + $this->matcher = $this->createMatcherFromHtml( $tagHtmlRepresentation ); + } + + public function describeTo( Description $description ) { + $description->appendText( 'tag matching outline `' ) + ->appendText( $this->tagHtmlOutline ) + ->appendText( '` ' ); + } + + /** + * @param \DOMElement $item + * @param Description $mismatchDescription + * + * @return bool + */ + protected function matchesSafelyWithDiagnosticDescription( $item, Description $mismatchDescription ) { + $result = $this->matcher->matches( $item ); + if ( !$result ) { + $mismatchDescription->appendText( 'was `' ) + ->appendText( $this->elementToString( $item ) ) + ->appendText( '`' ); + } + return $result; + } + + private function createMatcherFromHtml( $htmlOutline ) { + $document = $this->parseHtml( $htmlOutline ); + $targetTag = $this->getSingleTagFromThe( $document ); + + $this->assertTagDoesNotContainChildren( $targetTag ); + + $attributeMatchers = $this->createAttributeMatchers( $htmlOutline, $targetTag ); + $classMatchers = $this->createClassMatchers( $targetTag ); + + return AllOf::allOf( + new TagNameMatcher( IsEqual::equalTo( $targetTag->tagName ) ), + call_user_func_array( [ AllOf::class, 'allOf' ], $attributeMatchers ), + call_user_func_array( [ AllOf::class, 'allOf' ], $classMatchers ) + ); + } + + private function isUnknownTagError( \LibXMLError $error ) { + return $error->code === self::XML_UNKNOWN_TAG_ERROR_CODE; + } + + private function isBooleanAttribute( $inputHtml, $attributeName ) { + $quotedName = preg_quote( $attributeName, '/' ); + + $attributeHasValueAssigned = preg_match( "/\b{$quotedName}\s*=/ui", $inputHtml ); + return !$attributeHasValueAssigned; + } + + /** + * @param $html + * + * @return \DOMDocument + * @throws \InvalidArgumentException + */ + private function parseHtml( $html ) { + $internalErrors = libxml_use_internal_errors( true ); + $document = new \DOMDocument(); + + if ( !@$document->loadHTML( $html ) ) { + throw new \InvalidArgumentException( "There was some parsing error of `$html`" ); + } + + $errors = libxml_get_errors(); + libxml_clear_errors(); + libxml_use_internal_errors( $internalErrors ); + + /** @var \LibXMLError $error */ + foreach ( $errors as $error ) { + if ( $this->isUnknownTagError( $error ) ) { + continue; + } + + throw new \InvalidArgumentException( + 'There was parsing error: ' . trim( $error->message ) . ' on line ' . $error->line + ); + } + + return $document; + } + + /** + * @param \DOMDocument $document + * + * @return \DOMElement + * @throws \InvalidArgumentException + */ + private function getSingleTagFromThe( \DOMDocument $document ) { + $directChildren = iterator_to_array( $document->documentElement->childNodes ); + + $body = array_shift( $directChildren ); + $directChildren = iterator_to_array( $body->childNodes ); + + if ( count( $directChildren ) !== 1 ) { + throw new InvalidArgumentException( 'Expected exactly 1 tag description, got ' . count( $directChildren ) ); + } + + return $directChildren[0]; + } + + private function assertTagDoesNotContainChildren( \DOMElement $targetTag ) { + if ( $targetTag->childNodes->length > 0 ) { + throw new InvalidArgumentException( 'Nested elements are not allowed' ); + } + } + + /** + * @param string $inputHtml + * @param $targetTag + * @return AttributeMatcher[] + */ + private function createAttributeMatchers( $inputHtml, \DOMElement $targetTag ) { + $attributeMatchers = []; + /** @var \DOMAttr $attribute */ + foreach ( $targetTag->attributes as $attribute ) { + if ( $attribute->name === 'class' ) { + continue; + } + + $attributeMatcher = new AttributeMatcher( IsEqual::equalTo( $attribute->name ) ); + if ( !$this->isBooleanAttribute( $inputHtml, $attribute->name ) ) { + $attributeMatcher = $attributeMatcher->havingValue( IsEqual::equalTo( $attribute->value ) ); + } + + $attributeMatchers[] = $attributeMatcher; + } + return $attributeMatchers; + } + + /** + * @param \DOMElement $targetTag + * @return ClassMatcher[] + */ + private function createClassMatchers( $targetTag ) { + $classMatchers = []; + $classValue = $targetTag->getAttribute( 'class' ); + foreach ( explode( ' ', $classValue ) as $expectedClass ) { + if ( $expectedClass === '' ) { + continue; + } + $classMatchers[] = new ClassMatcher( IsEqual::equalTo( $expectedClass ) ); + } + return $classMatchers; + } + + private function elementToString( \DOMElement $element ) { + $newDocument = new \DOMDocument(); + $cloned = $element->cloneNode( true ); + $newDocument->appendChild( $newDocument->importNode( $cloned, true ) ); + return trim( $newDocument->saveHTML() ); + } } diff --git a/src/DirectChildElementMatcher.php b/src/DirectChildElementMatcher.php index 526023b..b8ed2f6 100644 --- a/src/DirectChildElementMatcher.php +++ b/src/DirectChildElementMatcher.php @@ -6,71 +6,67 @@ use Hamcrest\Matcher; use Hamcrest\TypeSafeDiagnosingMatcher; -class DirectChildElementMatcher extends TypeSafeDiagnosingMatcher -{ - - /** - * @var Matcher - */ - private $matcher; - - public static function havingDirectChild(Matcher $elementMatcher = null) { - return new static($elementMatcher); - } - - public function __construct(Matcher $matcher = null) - { - parent::__construct(\DOMNode::class); - $this->matcher = $matcher; - } - - public function describeTo(Description $description) - { - $description->appendText('having direct child '); - if ($this->matcher) { - $description->appendDescriptionOf($this->matcher); - } - } - - /** - * @param \DOMDocument|\DOMNode $item - * @param Description $mismatchDescription - * - * @return bool - */ - protected function matchesSafelyWithDiagnosticDescription($item, Description $mismatchDescription) - { - if ($item instanceof \DOMDocument) { - $directChildren = iterator_to_array($item->documentElement->childNodes); - - $body = array_shift($directChildren); - $directChildren = $body->childNodes; - } else { - $directChildren = $item->childNodes; - } - - if ($directChildren->length === 0) { - $mismatchDescription->appendText('with no direct children'); - return false; - } - - $childWord = $directChildren->length === 1 ? 'child' : 'children'; - - $mismatchDescription->appendText("with direct {$childWord} "); - - if (!$this->matcher) { - return count($directChildren) !== 0; - } - - foreach ($directChildren as $child) { - if ($this->matcher && $this->matcher->matches($child)) { - return true; - } - } - - $this->matcher->describeMismatch($child, $mismatchDescription); - - return false; - } +class DirectChildElementMatcher extends TypeSafeDiagnosingMatcher { + + /** + * @var Matcher + */ + private $matcher; + + public static function havingDirectChild( Matcher $elementMatcher = null ) { + return new static( $elementMatcher ); + } + + public function __construct( Matcher $matcher = null ) { + parent::__construct( \DOMNode::class ); + $this->matcher = $matcher; + } + + public function describeTo( Description $description ) { + $description->appendText( 'having direct child ' ); + if ( $this->matcher ) { + $description->appendDescriptionOf( $this->matcher ); + } + } + + /** + * @param \DOMDocument|\DOMNode $item + * @param Description $mismatchDescription + * + * @return bool + */ + protected function matchesSafelyWithDiagnosticDescription( $item, Description $mismatchDescription ) { + if ( $item instanceof \DOMDocument ) { + $directChildren = iterator_to_array( $item->documentElement->childNodes ); + + $body = array_shift( $directChildren ); + $directChildren = $body->childNodes; + } else { + $directChildren = $item->childNodes; + } + + if ( $directChildren->length === 0 ) { + $mismatchDescription->appendText( 'with no direct children' ); + return false; + } + + $childWord = $directChildren->length === 1 ? 'child' : 'children'; + + $mismatchDescription->appendText( "with direct {$childWord} " ); + + if ( !$this->matcher ) { + return count( $directChildren ) !== 0; + } + + foreach ( $directChildren as $child ) { + if ( $this->matcher && $this->matcher->matches( $child ) ) { + return true; + } + } + + $this->matcher->describeMismatch( $child, $mismatchDescription ); + + return false; + } } diff --git a/src/HtmlMatcher.php b/src/HtmlMatcher.php index 2bc10c0..1937d33 100644 --- a/src/HtmlMatcher.php +++ b/src/HtmlMatcher.php @@ -6,103 +6,96 @@ use Hamcrest\DiagnosingMatcher; use Hamcrest\Matcher; -class HtmlMatcher extends DiagnosingMatcher -{ +class HtmlMatcher extends DiagnosingMatcher { /** - * @link http://www.xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors - * @link https://github.com/Chronic-Dev/libxml2/blob/683f296a905710ff285c28b8644ef3a3d8be9486/include/libxml/xmlerror.h#L257 - */ - const XML_UNKNOWN_TAG_ERROR_CODE = 801; - - /** - * @var Matcher - */ - private $elementMatcher; - - /** - * @param Matcher $elementMatcher - * - * @return HtmlMatcher - */ - public static function htmlPiece(Matcher $elementMatcher = null) - { - return new static($elementMatcher); - } - - private function __construct(Matcher $elementMatcher = null) - { - $this->elementMatcher = $elementMatcher; - } - - public function describeTo(Description $description) - { - $description->appendText('valid html piece '); - if ($this->elementMatcher) { - $description->appendDescriptionOf($this->elementMatcher); - } - } - - protected function matchesWithDiagnosticDescription($html, Description $mismatchDescription) - { - $internalErrors = libxml_use_internal_errors(true); - $document = new \DOMDocument(); - - $html = $this->escapeScriptTagContents($html); - - if (!@$document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'))) { - $mismatchDescription->appendText('there was some parsing error'); - return false; - } - - $errors = libxml_get_errors(); - libxml_clear_errors(); - libxml_use_internal_errors($internalErrors); - - $result = true; - /** @var \LibXMLError $error */ - foreach ($errors as $error) { - if ($this->isUnknownTagError($error)) { - continue; - } - - $mismatchDescription->appendText('there was parsing error: ') - ->appendText(trim($error->message)) - ->appendText(' on line ') - ->appendText($error->line); - $result = false; - } - - if ($result === false) { - return $result; - } - $mismatchDescription->appendText('valid html piece '); - - if ($this->elementMatcher) { - $result = $this->elementMatcher->matches($document); - $this->elementMatcher->describeMismatch($document, $mismatchDescription); - } - - $mismatchDescription->appendText("\nActual html:\n")->appendText($html); - - return $result; - } - - private function isUnknownTagError(\LibXMLError $error) - { - return $error->code === self::XML_UNKNOWN_TAG_ERROR_CODE; - } - - /** - * @param string $html - * - * @return string HTML - */ - private function escapeScriptTagContents($html) - { - return preg_replace_callback('#()(.*)()#isU', function ($matches) { - return $matches[1] . str_replace('elementMatcher = $elementMatcher; + } + + public function describeTo( Description $description ) { + $description->appendText( 'valid html piece ' ); + if ( $this->elementMatcher ) { + $description->appendDescriptionOf( $this->elementMatcher ); + } + } + + protected function matchesWithDiagnosticDescription( $html, Description $mismatchDescription ) { + $internalErrors = libxml_use_internal_errors( true ); + $document = new \DOMDocument(); + + $html = $this->escapeScriptTagContents( $html ); + + if ( !@$document->loadHTML( mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ) ) ) { + $mismatchDescription->appendText( 'there was some parsing error' ); + return false; + } + + $errors = libxml_get_errors(); + libxml_clear_errors(); + libxml_use_internal_errors( $internalErrors ); + + $result = true; + /** @var \LibXMLError $error */ + foreach ( $errors as $error ) { + if ( $this->isUnknownTagError( $error ) ) { + continue; + } + + $mismatchDescription->appendText( 'there was parsing error: ' ) + ->appendText( trim( $error->message ) ) + ->appendText( ' on line ' ) + ->appendText( $error->line ); + $result = false; + } + + if ( $result === false ) { + return $result; + } + $mismatchDescription->appendText( 'valid html piece ' ); + + if ( $this->elementMatcher ) { + $result = $this->elementMatcher->matches( $document ); + $this->elementMatcher->describeMismatch( $document, $mismatchDescription ); + } + + $mismatchDescription->appendText( "\nActual html:\n" )->appendText( $html ); + + return $result; + } + + private function isUnknownTagError( \LibXMLError $error ) { + return $error->code === self::XML_UNKNOWN_TAG_ERROR_CODE; + } + + /** + * @param string $html + * + * @return string HTML + */ + private function escapeScriptTagContents( $html ) { + return preg_replace_callback( '#()(.*)()#isU', function ( $matches ) { + return $matches[1] . str_replace( 'tagMatcher = $tagMatcher; - } + public function __construct( Matcher $tagMatcher = null ) { + parent::__construct( self::TYPE_OBJECT, \DOMDocument::class ); + $this->tagMatcher = $tagMatcher; + } - public function describeTo(Description $description) - { - $description->appendText('having root element '); - if ($this->tagMatcher) { - $description->appendDescriptionOf($this->tagMatcher); - } - } + public function describeTo( Description $description ) { + $description->appendText( 'having root element ' ); + if ( $this->tagMatcher ) { + $description->appendDescriptionOf( $this->tagMatcher ); + } + } - /** - * @param \DOMDocument $item - * @param Description $mismatchDescription - * - * @return bool - */ - protected function matchesSafelyWithDiagnosticDescription($item, Description $mismatchDescription) - { - $DOMNodeList = iterator_to_array($item->documentElement->childNodes); + /** + * @param \DOMDocument $item + * @param Description $mismatchDescription + * + * @return bool + */ + protected function matchesSafelyWithDiagnosticDescription( $item, Description $mismatchDescription ) { + $DOMNodeList = iterator_to_array( $item->documentElement->childNodes ); - $body = array_shift($DOMNodeList); - $DOMNodeList = iterator_to_array($body->childNodes); - if (count($DOMNodeList) > 1) { - //TODO Test this description - $mismatchDescription->appendText('having ' . count($DOMNodeList) . ' root elements '); - return false; - } + $body = array_shift( $DOMNodeList ); + $DOMNodeList = iterator_to_array( $body->childNodes ); + if ( count( $DOMNodeList ) > 1 ) { + // TODO Test this description + $mismatchDescription->appendText( 'having ' . count( $DOMNodeList ) . ' root elements ' ); + return false; + } - $target = array_shift($DOMNodeList); - if (!$target) { - //TODO Reproduce? - $mismatchDescription->appendText('having no root elements '); - return false; - } - if ($this->tagMatcher) { - $mismatchDescription->appendText('root element '); - $this->tagMatcher->describeMismatch($target, $mismatchDescription); - return $this->tagMatcher->matches($target); - } + $target = array_shift( $DOMNodeList ); + if ( !$target ) { + // TODO Reproduce? + $mismatchDescription->appendText( 'having no root elements ' ); + return false; + } + if ( $this->tagMatcher ) { + $mismatchDescription->appendText( 'root element ' ); + $this->tagMatcher->describeMismatch( $target, $mismatchDescription ); + return $this->tagMatcher->matches( $target ); + } - return (bool)$target; - } + return (bool)$target; + } } diff --git a/src/TagMatcher.php b/src/TagMatcher.php index 172dd47..2ebacea 100644 --- a/src/TagMatcher.php +++ b/src/TagMatcher.php @@ -4,12 +4,10 @@ use Hamcrest\TypeSafeDiagnosingMatcher; -abstract class TagMatcher extends TypeSafeDiagnosingMatcher -{ +abstract class TagMatcher extends TypeSafeDiagnosingMatcher { - public function __construct() - { - parent::__construct(self::TYPE_OBJECT, \DOMElement::class); - } + public function __construct() { + parent::__construct( self::TYPE_OBJECT, \DOMElement::class ); + } } diff --git a/src/TagNameMatcher.php b/src/TagNameMatcher.php index d4b4cdf..db46329 100644 --- a/src/TagNameMatcher.php +++ b/src/TagNameMatcher.php @@ -6,41 +6,37 @@ use Hamcrest\Matcher; use Hamcrest\Util; -class TagNameMatcher extends TagMatcher -{ - - /** - * @var Matcher - */ - private $tagNameMatcher; - - public static function withTagName($tagName) { - return new static(Util::wrapValueWithIsEqual($tagName)); - } - - public function __construct(Matcher $tagNameMatcher) - { - parent::__construct(); - $this->tagNameMatcher = $tagNameMatcher; - } - - public function describeTo(Description $description) - { - $description->appendText('with tag name ') - ->appendDescriptionOf($this->tagNameMatcher); - } - - /** - * @param \DOMElement $item - * @param Description $mismatchDescription - * - * @return bool - */ - protected function matchesSafelyWithDiagnosticDescription($item, Description $mismatchDescription) - { - $mismatchDescription->appendText('tag name '); - $this->tagNameMatcher->describeMismatch($item->tagName, $mismatchDescription); - return $this->tagNameMatcher->matches($item->tagName); - } +class TagNameMatcher extends TagMatcher { + + /** + * @var Matcher + */ + private $tagNameMatcher; + + public static function withTagName( $tagName ) { + return new static( Util::wrapValueWithIsEqual( $tagName ) ); + } + + public function __construct( Matcher $tagNameMatcher ) { + parent::__construct(); + $this->tagNameMatcher = $tagNameMatcher; + } + + public function describeTo( Description $description ) { + $description->appendText( 'with tag name ' ) + ->appendDescriptionOf( $this->tagNameMatcher ); + } + + /** + * @param \DOMElement $item + * @param Description $mismatchDescription + * + * @return bool + */ + protected function matchesSafelyWithDiagnosticDescription( $item, Description $mismatchDescription ) { + $mismatchDescription->appendText( 'tag name ' ); + $this->tagNameMatcher->describeMismatch( $item->tagName, $mismatchDescription ); + return $this->tagNameMatcher->matches( $item->tagName ); + } } diff --git a/src/TextContentsMatcher.php b/src/TextContentsMatcher.php index d4ead34..0551309 100644 --- a/src/TextContentsMatcher.php +++ b/src/TextContentsMatcher.php @@ -6,38 +6,34 @@ use Hamcrest\Matcher; use Hamcrest\Util; -class TextContentsMatcher extends TagMatcher -{ - - /** - * @var Matcher - */ - private $matcher; - - public static function havingTextContents($text) { - return new static(Util::wrapValueWithIsEqual($text)); - } - - public function __construct(Matcher $matcher) - { - parent::__construct(); - $this->matcher = $matcher; - } - - public function describeTo(Description $description) - { - $description->appendText('having text contents ')->appendDescriptionOf($this->matcher); - } - - /** - * @param \DOMElement $item - * @param Description $mismatchDescription - * - * @return bool - */ - protected function matchesSafelyWithDiagnosticDescription($item, Description $mismatchDescription) - { - return $this->matcher->matches($item->textContent); - } +class TextContentsMatcher extends TagMatcher { + + /** + * @var Matcher + */ + private $matcher; + + public static function havingTextContents( $text ) { + return new static( Util::wrapValueWithIsEqual( $text ) ); + } + + public function __construct( Matcher $matcher ) { + parent::__construct(); + $this->matcher = $matcher; + } + + public function describeTo( Description $description ) { + $description->appendText( 'having text contents ' )->appendDescriptionOf( $this->matcher ); + } + + /** + * @param \DOMElement $item + * @param Description $mismatchDescription + * + * @return bool + */ + protected function matchesSafelyWithDiagnosticDescription( $item, Description $mismatchDescription ) { + return $this->matcher->matches( $item->textContent ); + } } diff --git a/src/XmlNodeRecursiveIterator.php b/src/XmlNodeRecursiveIterator.php index 2d28cc3..7949d5b 100644 --- a/src/XmlNodeRecursiveIterator.php +++ b/src/XmlNodeRecursiveIterator.php @@ -2,32 +2,29 @@ namespace WMDE\HamcrestHtml; -class XmlNodeRecursiveIterator extends \ArrayIterator -{ +class XmlNodeRecursiveIterator extends \ArrayIterator { - public function __construct(\DOMNodeList $nodeList) - { - $queue = $this->addElementsToQueue([], $nodeList); - parent::__construct($queue); - } + public function __construct( \DOMNodeList $nodeList ) { + $queue = $this->addElementsToQueue( [], $nodeList ); + parent::__construct( $queue ); + } - /** - * @param array $queue - * @param \DOMNodeList $nodeList - * - * @return array New queue - */ - private function addElementsToQueue(array $queue, \DOMNodeList $nodeList) - { - /** @var \DOMElement $node */ - foreach ($nodeList as $node) { - $queue[] = $node; - if ($node->childNodes !== null) { - $queue = $this->addElementsToQueue($queue, $node->childNodes); - } - } + /** + * @param array $queue + * @param \DOMNodeList $nodeList + * + * @return array New queue + */ + private function addElementsToQueue( array $queue, \DOMNodeList $nodeList ) { + /** @var \DOMElement $node */ + foreach ( $nodeList as $node ) { + $queue[] = $node; + if ( $node->childNodes !== null ) { + $queue = $this->addElementsToQueue( $queue, $node->childNodes ); + } + } - return $queue; - } + return $queue; + } } diff --git a/src/functions.php b/src/functions.php index 0cf5672..e0e2054 100644 --- a/src/functions.php +++ b/src/functions.php @@ -2,88 +2,88 @@ use Hamcrest\Matcher; -if (!function_exists('htmlPiece')) { - /** - * @param mixed $elementMatcher - * - * @return \WMDE\HamcrestHtml\HtmlMatcher - */ - function htmlPiece(Matcher $elementMatcher = null) { - return \WMDE\HamcrestHtml\HtmlMatcher::htmlPiece($elementMatcher); - } +if ( !function_exists( 'htmlPiece' ) ) { + /** + * @param mixed $elementMatcher + * + * @return \WMDE\HamcrestHtml\HtmlMatcher + */ + function htmlPiece( Matcher $elementMatcher = null ) { + return \WMDE\HamcrestHtml\HtmlMatcher::htmlPiece( $elementMatcher ); + } } -if (!function_exists('havingRootElement')) { - function havingRootElement(Matcher $matcher = null) { - return \WMDE\HamcrestHtml\RootElementMatcher::havingRootElement($matcher); - } +if ( !function_exists( 'havingRootElement' ) ) { + function havingRootElement( Matcher $matcher = null ) { + return \WMDE\HamcrestHtml\RootElementMatcher::havingRootElement( $matcher ); + } } -if (!function_exists('havingDirectChild')) { - function havingDirectChild(Matcher $elementMatcher = null) { - return \WMDE\HamcrestHtml\DirectChildElementMatcher::havingDirectChild($elementMatcher); - } +if ( !function_exists( 'havingDirectChild' ) ) { + function havingDirectChild( Matcher $elementMatcher = null ) { + return \WMDE\HamcrestHtml\DirectChildElementMatcher::havingDirectChild( $elementMatcher ); + } } -if (!function_exists('havingChild')) { - function havingChild(Matcher $elementMatcher = null) { - return \WMDE\HamcrestHtml\ChildElementMatcher::havingChild($elementMatcher); - } +if ( !function_exists( 'havingChild' ) ) { + function havingChild( Matcher $elementMatcher = null ) { + return \WMDE\HamcrestHtml\ChildElementMatcher::havingChild( $elementMatcher ); + } } -if (!function_exists('withTagName')) { - /** - * @param Matcher|string $tagName - * - * @return \WMDE\HamcrestHtml\TagNameMatcher - */ - function withTagName($tagName) { - return \WMDE\HamcrestHtml\TagNameMatcher::withTagName($tagName); - } +if ( !function_exists( 'withTagName' ) ) { + /** + * @param Matcher|string $tagName + * + * @return \WMDE\HamcrestHtml\TagNameMatcher + */ + function withTagName( $tagName ) { + return \WMDE\HamcrestHtml\TagNameMatcher::withTagName( $tagName ); + } } -if (!function_exists('withAttribute')) { - /** - * @param Matcher|string $attributeName - * - * @return \WMDE\HamcrestHtml\AttributeMatcher - */ - function withAttribute($attributeName) { - return \WMDE\HamcrestHtml\AttributeMatcher::withAttribute($attributeName); - } +if ( !function_exists( 'withAttribute' ) ) { + /** + * @param Matcher|string $attributeName + * + * @return \WMDE\HamcrestHtml\AttributeMatcher + */ + function withAttribute( $attributeName ) { + return \WMDE\HamcrestHtml\AttributeMatcher::withAttribute( $attributeName ); + } } -if (!function_exists('withClass')) { - /** - * @param Matcher|string $class - * - * @return \WMDE\HamcrestHtml\ClassMatcher - */ - function withClass($class) { - //TODO don't allow to call with empty string +if ( !function_exists( 'withClass' ) ) { + /** + * @param Matcher|string $class + * + * @return \WMDE\HamcrestHtml\ClassMatcher + */ + function withClass( $class ) { + // TODO don't allow to call with empty string - return \WMDE\HamcrestHtml\ClassMatcher::withClass($class); - } + return \WMDE\HamcrestHtml\ClassMatcher::withClass( $class ); + } } -if (!function_exists('havingTextContents')) { - /** - * @param Matcher|string $text - * - * @return \WMDE\HamcrestHtml\TextContentsMatcher - */ - function havingTextContents($text) { - return \WMDE\HamcrestHtml\TextContentsMatcher::havingTextContents($text); - } +if ( !function_exists( 'havingTextContents' ) ) { + /** + * @param Matcher|string $text + * + * @return \WMDE\HamcrestHtml\TextContentsMatcher + */ + function havingTextContents( $text ) { + return \WMDE\HamcrestHtml\TextContentsMatcher::havingTextContents( $text ); + } } -if (!function_exists('tagMatchingOutline')) { - /** - * @param string $htmlOutline - * - * @return \WMDE\HamcrestHtml\ComplexTagMatcher - */ - function tagMatchingOutline($htmlOutline) { - return \WMDE\HamcrestHtml\ComplexTagMatcher::tagMatchingOutline($htmlOutline); - } +if ( !function_exists( 'tagMatchingOutline' ) ) { + /** + * @param string $htmlOutline + * + * @return \WMDE\HamcrestHtml\ComplexTagMatcher + */ + function tagMatchingOutline( $htmlOutline ) { + return \WMDE\HamcrestHtml\ComplexTagMatcher::tagMatchingOutline( $htmlOutline ); + } } diff --git a/tests/ComplexTagMatcherTest.php b/tests/ComplexTagMatcherTest.php index 5e86dba..4183164 100644 --- a/tests/ComplexTagMatcherTest.php +++ b/tests/ComplexTagMatcherTest.php @@ -8,134 +8,133 @@ /** * @covers WMDE\HamcrestHtml\ComplexTagMatcher */ -class ComplexTagMatcherTest extends \PHPUnit_Framework_TestCase -{ - - /** - * @test - */ - public function assertPasses_WhenTagInHtmlHasSameTagName() { - $html = '

'; - - assertThat($html, is(htmlPiece(havingChild(ComplexTagMatcher::tagMatchingOutline('

'))))); - } - - /** - * @test - */ - public function assertFails_WhenTagInHtmlIsDiffersFromGivenTagName() { - $html = ''; - - $this->setExpectedException(AssertionError::class); - assertThat($html, is(htmlPiece(havingChild(ComplexTagMatcher::tagMatchingOutline('

'))))); - } - - /** - * @test - */ - public function canNotCreateMatcherWithEmptyDescription() { - $this->setExpectedException(\Exception::class); - ComplexTagMatcher::tagMatchingOutline(''); - } - - /** - * @test - */ - public function canNotCreateMatcherExpectingTwoElements() { - $this->setExpectedException(\Exception::class); - ComplexTagMatcher::tagMatchingOutline('

'); - } - - /** - * @test - */ - public function canNotCreateMatcherWithChildElement() { - $this->setExpectedException(\Exception::class); - ComplexTagMatcher::tagMatchingOutline('

'); - } - - /** - * @test - */ - public function assertFails_WhenTagInHtmlDoesNotHaveExpectedAttribute() { - $html = '

'; - - $this->setExpectedException(AssertionError::class); - assertThat($html, is(htmlPiece(havingChild( - ComplexTagMatcher::tagMatchingOutline('

'))))); - } - - /** - * @test - */ - public function assertPasses_WhenTagInHtmlHasExpectedAttribute() { - $html = '

'; - - assertThat($html, is(htmlPiece(havingChild( - ComplexTagMatcher::tagMatchingOutline('

'))))); - } - - /** - * @test - */ - public function assertFails_WhenTagInHtmlDoesNotHaveAllExpectedAttribute() { - $html = '

'; - - $this->setExpectedException(AssertionError::class); - assertThat($html, is(htmlPiece(havingChild( - ComplexTagMatcher::tagMatchingOutline('

'))))); - } - - /** - * @test - */ - public function assertPasses_WhenExpectBooleanAttributeButItIsThereWithSomeValue() { - $html = ''; - - assertThat($html, is(htmlPiece(havingChild( - ComplexTagMatcher::tagMatchingOutline(''))))); - } - - /** - * @test - */ - public function assertFails_WhenExpectAttributeWithEmptyValueButItIsNotEmpty() { - $html = ''; - - $this->setExpectedException(AssertionError::class); - assertThat($html, is(htmlPiece(havingChild( - ComplexTagMatcher::tagMatchingOutline(''))))); - } - - /** - * @test - */ - public function assertPasses_WhenGivenTagHasExpectedClass() { - $html = ''; - - assertThat($html, is(htmlPiece(havingChild( - ComplexTagMatcher::tagMatchingOutline(''))))); - } - - /** - * @test - */ - public function assertFails_WhenGivenTagDoesNotHaveExpectedClass() { - $html = ''; - - $this->setExpectedException(AssertionError::class); - assertThat($html, is(htmlPiece(havingChild( - ComplexTagMatcher::tagMatchingOutline(''))))); - } - - /** - * @test - */ - public function toleratesExtraSpacesInClassDescription() { - $html = ''; - - assertThat($html, is(htmlPiece(havingChild( - ComplexTagMatcher::tagMatchingOutline(''))))); - } +class ComplexTagMatcherTest extends \PHPUnit\Framework\TestCase { + + /** + * @test + */ + public function assertPasses_WhenTagInHtmlHasSameTagName() { + $html = '

'; + + assertThat( $html, is( htmlPiece( havingChild( ComplexTagMatcher::tagMatchingOutline( '

' ) ) ) ) ); + } + + /** + * @test + */ + public function assertFails_WhenTagInHtmlIsDiffersFromGivenTagName() { + $html = ''; + + $this->setExpectedException( AssertionError::class ); + assertThat( $html, is( htmlPiece( havingChild( ComplexTagMatcher::tagMatchingOutline( '

' ) ) ) ) ); + } + + /** + * @test + */ + public function canNotCreateMatcherWithEmptyDescription() { + $this->setExpectedException( \Exception::class ); + ComplexTagMatcher::tagMatchingOutline( '' ); + } + + /** + * @test + */ + public function canNotCreateMatcherExpectingTwoElements() { + $this->setExpectedException( \Exception::class ); + ComplexTagMatcher::tagMatchingOutline( '

' ); + } + + /** + * @test + */ + public function canNotCreateMatcherWithChildElement() { + $this->setExpectedException( \Exception::class ); + ComplexTagMatcher::tagMatchingOutline( '

' ); + } + + /** + * @test + */ + public function assertFails_WhenTagInHtmlDoesNotHaveExpectedAttribute() { + $html = '

'; + + $this->setExpectedException( AssertionError::class ); + assertThat( $html, is( htmlPiece( havingChild( + ComplexTagMatcher::tagMatchingOutline( '

' ) ) ) ) ); + } + + /** + * @test + */ + public function assertPasses_WhenTagInHtmlHasExpectedAttribute() { + $html = '

'; + + assertThat( $html, is( htmlPiece( havingChild( + ComplexTagMatcher::tagMatchingOutline( '

' ) ) ) ) ); + } + + /** + * @test + */ + public function assertFails_WhenTagInHtmlDoesNotHaveAllExpectedAttribute() { + $html = '

'; + + $this->setExpectedException( AssertionError::class ); + assertThat( $html, is( htmlPiece( havingChild( + ComplexTagMatcher::tagMatchingOutline( '

' ) ) ) ) ); + } + + /** + * @test + */ + public function assertPasses_WhenExpectBooleanAttributeButItIsThereWithSomeValue() { + $html = ''; + + assertThat( $html, is( htmlPiece( havingChild( + ComplexTagMatcher::tagMatchingOutline( '' ) ) ) ) ); + } + + /** + * @test + */ + public function assertFails_WhenExpectAttributeWithEmptyValueButItIsNotEmpty() { + $html = ''; + + $this->setExpectedException( AssertionError::class ); + assertThat( $html, is( htmlPiece( havingChild( + ComplexTagMatcher::tagMatchingOutline( '' ) ) ) ) ); + } + + /** + * @test + */ + public function assertPasses_WhenGivenTagHasExpectedClass() { + $html = ''; + + assertThat( $html, is( htmlPiece( havingChild( + ComplexTagMatcher::tagMatchingOutline( '' ) ) ) ) ); + } + + /** + * @test + */ + public function assertFails_WhenGivenTagDoesNotHaveExpectedClass() { + $html = ''; + + $this->setExpectedException( AssertionError::class ); + assertThat( $html, is( htmlPiece( havingChild( + ComplexTagMatcher::tagMatchingOutline( '' ) ) ) ) ); + } + + /** + * @test + */ + public function toleratesExtraSpacesInClassDescription() { + $html = ''; + + assertThat( $html, is( htmlPiece( havingChild( + ComplexTagMatcher::tagMatchingOutline( '' ) ) ) ) ); + } } diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 11a423b..75d464d 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -5,198 +5,194 @@ use Hamcrest\AssertionError; use Hamcrest\Matcher; -class FunctionsTest extends \PHPUnit_Framework_TestCase -{ +class FunctionsTest extends \PHPUnit\Framework\TestCase { - /** - * @test - */ - public function havingRootElement_MultipleRootTags_ThrowsException() - { - //TODO Does it make sense? - $html = '

'; + /** + * @test + */ + public function havingRootElement_MultipleRootTags_ThrowsException() { + // TODO Does it make sense? + $html = '

'; - $this->setExpectedException(AssertionError::class); - assertThat($html, is(htmlPiece(havingRootElement()))); - } + $this->setExpectedException( AssertionError::class ); + assertThat( $html, is( htmlPiece( havingRootElement() ) ) ); + } - /** - * @test - * @dataProvider dataProvider_ElementExists - */ - public function matcherCanFindElement($html, $matcher) { - assertThat($html, is(htmlPiece($matcher))); - } + /** + * @test + * @dataProvider dataProvider_ElementExists + */ + public function matcherCanFindElement( $html, $matcher ) { + assertThat( $html, is( htmlPiece( $matcher ) ) ); + } - /** - * @test - * @dataProvider dataProvider_ElementDoesNotExist - */ - public function matcherCantFindElement($html, $matcher, Matcher $messageMatcher) { - $thrownException = null; - try { - assertThat($html, is(htmlPiece($matcher))); - } catch (\Exception $e) { - $thrownException = $e; - } + /** + * @test + * @dataProvider dataProvider_ElementDoesNotExist + */ + public function matcherCantFindElement( $html, $matcher, Matcher $messageMatcher ) { + $thrownException = null; + try { + assertThat( $html, is( htmlPiece( $matcher ) ) ); + } catch ( \Exception $e ) { + $thrownException = $e; + } - assertThat($thrownException, is(anInstanceOf(AssertionError::class))); - assertThat($thrownException->getMessage(), $messageMatcher); - } + assertThat( $thrownException, is( anInstanceOf( AssertionError::class ) ) ); + assertThat( $thrownException->getMessage(), $messageMatcher ); + } - public function dataProvider_ElementExists() - { - return [ - 'htmlPiece - simple case' => [ - '

', - null - ], - 'havingRootElement - has root element' => [ - '

', - havingRootElement() - ], - 'withTagName - simple case' => [ - '

', - havingRootElement(withTagName('p')) - ], - 'havingDirectChild - without qualifier' => [ - '

', - havingDirectChild() - ], - 'havingDirectChild - nested structure' => [ - '

', - havingDirectChild(havingDirectChild(withTagName('b'))) - ], - 'havingChild - target tag is not first' => [ - '', - havingChild(withTagName('b')) - ], - 'havingChild - target tag is nested' => [ - '

', - havingChild(withTagName('i')) - ], - 'withAttribute - select element by attribute name only' => [ - '

', - havingChild(withAttribute('name')) - ], - 'withAttribute - select element by attribute name and value' => [ - '

', - havingChild(withAttribute('name')->havingValue('something')) - ], - 'withClass - exact match' => [ - '

', - havingChild(withClass('test-class')) - ], - 'withClass - one of the classes' => [ - '

', - havingChild(withClass('class2')) - ], - 'withClass - classes separated with tab' => [ - "

", - havingChild(withClass('class2')) - ], - 'havingTextContents' => [ - '

this is some text

', - havingChild(havingTextContents(containsString('some text'))) - ], - 'havingTextContents - unicode text' => [ - '

какой-то текст

', - havingChild(havingTextContents(containsString('какой-то текст'))) - ], - 'tagMatchingOutline' => [ - '
', - havingChild(tagMatchingOutline('')) - ], - ]; - } + public function dataProvider_ElementExists() { + return [ + 'htmlPiece - simple case' => [ + '

', + null + ], + 'havingRootElement - has root element' => [ + '

', + havingRootElement() + ], + 'withTagName - simple case' => [ + '

', + havingRootElement( withTagName( 'p' ) ) + ], + 'havingDirectChild - without qualifier' => [ + '

', + havingDirectChild() + ], + 'havingDirectChild - nested structure' => [ + '

', + havingDirectChild( havingDirectChild( withTagName( 'b' ) ) ) + ], + 'havingChild - target tag is not first' => [ + '', + havingChild( withTagName( 'b' ) ) + ], + 'havingChild - target tag is nested' => [ + '

', + havingChild( withTagName( 'i' ) ) + ], + 'withAttribute - select element by attribute name only' => [ + '

', + havingChild( withAttribute( 'name' ) ) + ], + 'withAttribute - select element by attribute name and value' => [ + '

', + havingChild( withAttribute( 'name' )->havingValue( 'something' ) ) + ], + 'withClass - exact match' => [ + '

', + havingChild( withClass( 'test-class' ) ) + ], + 'withClass - one of the classes' => [ + '

', + havingChild( withClass( 'class2' ) ) + ], + 'withClass - classes separated with tab' => [ + "

", + havingChild( withClass( 'class2' ) ) + ], + 'havingTextContents' => [ + '

this is some text

', + havingChild( havingTextContents( containsString( 'some text' ) ) ) + ], + 'havingTextContents - unicode text' => [ + '

какой-то текст

', + havingChild( havingTextContents( containsString( 'какой-то текст' ) ) ) + ], + 'tagMatchingOutline' => [ + '
', + havingChild( tagMatchingOutline( '' ) ) + ], + ]; + } - public function dataProvider_ElementDoesNotExist() - { - return [ - 'htmlPiece - messed up tags' => [ - '

', - null, - allOf(containsString('html piece'), containsString('there was parsing error')) - ], - 'htmlPiece - prints passed html on failure' => [ - '

', - havingRootElement(withTagName('b')), - containsString('

') - ], - 'withTagName - simple case' => [ - '

', - havingRootElement(withTagName('b')), - allOf(containsString('having root element'), - containsString('with tag name "b"'), - containsString('root element tag name was "p"')), - ], - 'havingDirectChild - no direct child' => [ - '

', - havingDirectChild(havingDirectChild()), - allOf(containsString('having direct child'), - containsString('with direct child with no direct children')), - ], - 'havingDirectChild - single element' => [ - '

', - havingDirectChild(withTagName('b')), - allOf(containsString('having direct child'), - containsString('with tag name "b"')), - ], - 'havingDirectChild - nested matcher' => [ - '

', - havingDirectChild(havingDirectChild(withTagName('p'))), - both(containsString('having direct child having direct child with tag name "p"')) - ->andAlso(containsString('direct child with direct child tag name was "b"')) - ], - 'havingChild - no children' => [ - '

', - havingDirectChild(havingChild()), - both(containsString('having direct child having child')) - ->andAlso(containsString('having no children')) - ], - 'havingChild - target tag is absent' => [ - '

', - havingChild(withTagName('br')), - both(containsString('having child with tag name "br"')) - ->andAlso(containsString('having no children with tag name "br"')) - ], + public function dataProvider_ElementDoesNotExist() { + return [ + 'htmlPiece - messed up tags' => [ + '

', + null, + allOf( containsString( 'html piece' ), containsString( 'there was parsing error' ) ) + ], + 'htmlPiece - prints passed html on failure' => [ + '

', + havingRootElement( withTagName( 'b' ) ), + containsString( '

' ) + ], + 'withTagName - simple case' => [ + '

', + havingRootElement( withTagName( 'b' ) ), + allOf( containsString( 'having root element' ), + containsString( 'with tag name "b"' ), + containsString( 'root element tag name was "p"' ) ), + ], + 'havingDirectChild - no direct child' => [ + '

', + havingDirectChild( havingDirectChild() ), + allOf( containsString( 'having direct child' ), + containsString( 'with direct child with no direct children' ) ), + ], + 'havingDirectChild - single element' => [ + '

', + havingDirectChild( withTagName( 'b' ) ), + allOf( containsString( 'having direct child' ), + containsString( 'with tag name "b"' ) ), + ], + 'havingDirectChild - nested matcher' => [ + '

', + havingDirectChild( havingDirectChild( withTagName( 'p' ) ) ), + both( containsString( 'having direct child having direct child with tag name "p"' ) ) + ->andAlso( containsString( 'direct child with direct child tag name was "b"' ) ) + ], + 'havingChild - no children' => [ + '

', + havingDirectChild( havingChild() ), + both( containsString( 'having direct child having child' ) ) + ->andAlso( containsString( 'having no children' ) ) + ], + 'havingChild - target tag is absent' => [ + '

', + havingChild( withTagName( 'br' ) ), + both( containsString( 'having child with tag name "br"' ) ) + ->andAlso( containsString( 'having no children with tag name "br"' ) ) + ], - 'withAttribute - select element by attribute name only' => [ - '

', - havingChild(withAttribute('value')), - both(containsString('having child with attribute "value"')) - ->andAlso(containsString('having no children with attribute "value"')) - ], - 'withAttribute - select element by attribute name and value' => [ - '

', - havingChild(withAttribute('name')->havingValue('something')), - both(containsString('having child with attribute "name" having value "something"')) - ->andAlso(containsString('having no children with attribute "name" having value "something"')) - ], - 'withClass - no class' => [ - '

', - havingChild(withClass('test-class')), - both(containsString('having child with class "test-class"')) - ->andAlso(containsString('having no children with class "test-class"')) - ], - 'havingTextContents' => [ - '

this is some text

', - havingChild(havingTextContents('this is another text')), - both(containsString('having child having text contents "this is another text"')) - ->andAlso(containsString('no children having text contents "this is another text"')) - ], - 'havingTextContents - does not respect text in comments;' => [ - '
', - havingChild(havingTextContents('commented text')), - anything() - ], - 'tagMatchingOutline' => [ - '', - havingRootElement(tagMatchingOutline('')), - both(containsString('matching outline ``')) - ->andAlso(containsString('was ``')) - ], - ]; - } + 'withAttribute - select element by attribute name only' => [ + '

', + havingChild( withAttribute( 'value' ) ), + both( containsString( 'having child with attribute "value"' ) ) + ->andAlso( containsString( 'having no children with attribute "value"' ) ) + ], + 'withAttribute - select element by attribute name and value' => [ + '

', + havingChild( withAttribute( 'name' )->havingValue( 'something' ) ), + both( containsString( 'having child with attribute "name" having value "something"' ) ) + ->andAlso( containsString( 'having no children with attribute "name" having value "something"' ) ) + ], + 'withClass - no class' => [ + '

', + havingChild( withClass( 'test-class' ) ), + both( containsString( 'having child with class "test-class"' ) ) + ->andAlso( containsString( 'having no children with class "test-class"' ) ) + ], + 'havingTextContents' => [ + '

this is some text

', + havingChild( havingTextContents( 'this is another text' ) ), + both( containsString( 'having child having text contents "this is another text"' ) ) + ->andAlso( containsString( 'no children having text contents "this is another text"' ) ) + ], + 'havingTextContents - does not respect text in comments;' => [ + '
', + havingChild( havingTextContents( 'commented text' ) ), + anything() + ], + 'tagMatchingOutline' => [ + '', + havingRootElement( tagMatchingOutline( '' ) ), + both( containsString( 'matching outline ``' ) ) + ->andAlso( containsString( 'was ``' ) ) + ], + ]; + } } diff --git a/tests/HtmlMatcherTest.php b/tests/HtmlMatcherTest.php index 2e70d3d..330799b 100644 --- a/tests/HtmlMatcherTest.php +++ b/tests/HtmlMatcherTest.php @@ -7,103 +7,97 @@ /** * @covers WMDE\HamcrestHtml\HtmlMatcher */ -class HtmlMatcherTest extends \PHPUnit_Framework_TestCase -{ +class HtmlMatcherTest extends \PHPUnit\Framework\TestCase { - /** - * @test - * @dataProvider dataProvider_HtmlTagNamesIntroducedInHtml5 - */ - public function considersValidHtml_WhenUnknownForHtmlParserTagIsGiven($tagIntroducedInHtml5) - { - $html = "<$tagIntroducedInHtml5>"; + /** + * @test + * @dataProvider dataProvider_HtmlTagNamesIntroducedInHtml5 + */ + public function considersValidHtml_WhenUnknownForHtmlParserTagIsGiven( $tagIntroducedInHtml5 ) { + $html = "<$tagIntroducedInHtml5>"; - assertThat($html, is(HtmlMatcher::htmlPiece())); - } + assertThat( $html, is( HtmlMatcher::htmlPiece() ) ); + } - public function dataProvider_HtmlTagNamesIntroducedInHtml5() - { - return [ - 'article' => ['article'], - 'aside' => ['aside'], - 'bdi' => ['bdi'], - 'details' => ['details'], - 'dialog' => ['dialog'], - 'figcaption' => ['figcaption'], - 'figure' => ['figure'], - 'footer' => ['footer'], - 'header' => ['header'], - 'main' => ['main'], - 'mark' => ['mark'], - 'menuitem' => ['menuitem'], - 'meter' => ['meter'], - 'nav' => ['nav'], - 'progress' => ['progress'], - 'rp' => ['rp'], - 'rt' => ['rt'], - 'ruby' => ['ruby'], - 'section' => ['section'], - 'summary' => ['summary'], - 'time' => ['time'], - 'wbr' => ['wbr'], - 'datalist' => ['datalist'], - 'keygen' => ['keygen'], - 'output' => ['output'], - 'canvas' => ['canvas'], - 'svg' => ['svg'], - 'audio' => ['audio'], - 'embed' => ['embed'], - 'source' => ['source'], - 'track' => ['track'], - 'video' => ['video'], - ]; - } + public function dataProvider_HtmlTagNamesIntroducedInHtml5() { + return [ + 'article' => [ 'article' ], + 'aside' => [ 'aside' ], + 'bdi' => [ 'bdi' ], + 'details' => [ 'details' ], + 'dialog' => [ 'dialog' ], + 'figcaption' => [ 'figcaption' ], + 'figure' => [ 'figure' ], + 'footer' => [ 'footer' ], + 'header' => [ 'header' ], + 'main' => [ 'main' ], + 'mark' => [ 'mark' ], + 'menuitem' => [ 'menuitem' ], + 'meter' => [ 'meter' ], + 'nav' => [ 'nav' ], + 'progress' => [ 'progress' ], + 'rp' => [ 'rp' ], + 'rt' => [ 'rt' ], + 'ruby' => [ 'ruby' ], + 'section' => [ 'section' ], + 'summary' => [ 'summary' ], + 'time' => [ 'time' ], + 'wbr' => [ 'wbr' ], + 'datalist' => [ 'datalist' ], + 'keygen' => [ 'keygen' ], + 'output' => [ 'output' ], + 'canvas' => [ 'canvas' ], + 'svg' => [ 'svg' ], + 'audio' => [ 'audio' ], + 'embed' => [ 'embed' ], + 'source' => [ 'source' ], + 'track' => [ 'track' ], + 'video' => [ 'video' ], + ]; + } - /** - * @test - */ - public function considersValidHtml_WHtmlContainsScriptTagWithHtmlContents() - { - $html = "
+ /** + * @test + */ + public function considersValidHtml_WHtmlContainsScriptTagWithHtmlContents() { + $html = "
"; - assertThat($html, is(HtmlMatcher::htmlPiece())); - } + assertThat( $html, is( HtmlMatcher::htmlPiece() ) ); + } - /** - * @test - */ - public function addsSpecificTextInsideTheScriptTagsInsteadOfItsContents() - { - $html = "
+ /** + * @test + */ + public function addsSpecificTextInsideTheScriptTagsInsteadOfItsContents() { + $html = "
"; - assertThat($html, is(htmlPiece(havingChild( - both(withTagName('script')) - ->andAlso(havingTextContents("<\\/span>")))))); - } + assertThat( $html, is( htmlPiece( havingChild( + both( withTagName( 'script' ) ) + ->andAlso( havingTextContents( "<\\/span>" ) ) ) ) ) ); + } - /** - * @test - */ - public function doesNotTouchScriptTagAttributes() - { - $html = "
+ /** + * @test + */ + public function doesNotTouchScriptTagAttributes() { + $html = "
"; - assertThat($html, is(htmlPiece(havingChild( - allOf( - withTagName('script'), - withAttribute('type')->havingValue('x-template'), - withAttribute('attr1')->havingValue('value1') - ))))); - } + assertThat( $html, is( htmlPiece( havingChild( + allOf( + withTagName( 'script' ), + withAttribute( 'type' )->havingValue( 'x-template' ), + withAttribute( 'attr1' )->havingValue( 'value1' ) + ) ) ) ) ); + } } diff --git a/tests/XmlNodeRecursiveIteratorTest.php b/tests/XmlNodeRecursiveIteratorTest.php index bd01c08..4cda6f5 100644 --- a/tests/XmlNodeRecursiveIteratorTest.php +++ b/tests/XmlNodeRecursiveIteratorTest.php @@ -7,57 +7,52 @@ /** * @covers WMDE\HamcrestHtml\XmlNodeRecursiveIterator */ -class XmlNodeRecursiveIteratorTest extends \PHPUnit_Framework_TestCase -{ - - public function testIteratesAllElements_WhenFlatStructureGiven() - { - $DOMNodeList = $this->createDomNodeListFromHtml('

'); - - $recursiveIterator = new XmlNodeRecursiveIterator($DOMNodeList); - - $tagNames = $this->collectTagNames($recursiveIterator); - assertThat($tagNames, is(equalTo(['p']))); - } - - public function testIteratesElementsInAnyOrder_WhenNestedStructureGiven() - { - $DOMNodeList = $this->createDomNodeListFromHtml(''); - - $recursiveIterator = new XmlNodeRecursiveIterator($DOMNodeList); - - $tagNames = $this->collectTagNames($recursiveIterator); - assertThat($tagNames, is(containsInAnyOrder('a1', 'b1', 'b2', 'c21'))); - } - - private function createDomNodeListFromHtml($html) - { - $internalErrors = libxml_use_internal_errors(true); - $DOMDocument = new \DOMDocument(); - - if (!@$DOMDocument->loadHTML($html)) { - throw new \RuntimeException('Filed to parse HTML'); - } - - libxml_clear_errors(); - libxml_use_internal_errors($internalErrors); - $DOMNodeList = iterator_to_array($DOMDocument->documentElement->childNodes); - - $body = array_shift($DOMNodeList); - return $body->childNodes; - } - - /** - * @param $recursiveIterator - * @return array - */ - protected function collectTagNames($recursiveIterator) - { - $array = iterator_to_array($recursiveIterator); - $tagNames = array_map(function (\DOMElement $node) { - return $node->tagName; - }, $array); - return $tagNames; - } +class XmlNodeRecursiveIteratorTest extends \PHPUnit\Framework\TestCase { + + public function testIteratesAllElements_WhenFlatStructureGiven() { + $DOMNodeList = $this->createDomNodeListFromHtml( '

' ); + + $recursiveIterator = new XmlNodeRecursiveIterator( $DOMNodeList ); + + $tagNames = $this->collectTagNames( $recursiveIterator ); + assertThat( $tagNames, is( equalTo( [ 'p' ] ) ) ); + } + + public function testIteratesElementsInAnyOrder_WhenNestedStructureGiven() { + $DOMNodeList = $this->createDomNodeListFromHtml( '' ); + + $recursiveIterator = new XmlNodeRecursiveIterator( $DOMNodeList ); + + $tagNames = $this->collectTagNames( $recursiveIterator ); + assertThat( $tagNames, is( containsInAnyOrder( 'a1', 'b1', 'b2', 'c21' ) ) ); + } + + private function createDomNodeListFromHtml( $html ) { + $internalErrors = libxml_use_internal_errors( true ); + $DOMDocument = new \DOMDocument(); + + if ( !@$DOMDocument->loadHTML( $html ) ) { + throw new \RuntimeException( 'Filed to parse HTML' ); + } + + libxml_clear_errors(); + libxml_use_internal_errors( $internalErrors ); + $DOMNodeList = iterator_to_array( $DOMDocument->documentElement->childNodes ); + + $body = array_shift( $DOMNodeList ); + return $body->childNodes; + } + + /** + * @param $recursiveIterator + * @return array + */ + protected function collectTagNames( $recursiveIterator ) { + $array = iterator_to_array( $recursiveIterator ); + $tagNames = array_map( function ( \DOMElement $node ) { + return $node->tagName; + }, $array ); + return $tagNames; + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 2bcc433..547271a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,6 +1,6 @@