summaryrefslogtreecommitdiff
path: root/www/wiki/skins/chameleon/tests/phpunit/Unit/Components/GenericComponentTestCase.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/skins/chameleon/tests/phpunit/Unit/Components/GenericComponentTestCase.php')
-rw-r--r--www/wiki/skins/chameleon/tests/phpunit/Unit/Components/GenericComponentTestCase.php345
1 files changed, 345 insertions, 0 deletions
diff --git a/www/wiki/skins/chameleon/tests/phpunit/Unit/Components/GenericComponentTestCase.php b/www/wiki/skins/chameleon/tests/phpunit/Unit/Components/GenericComponentTestCase.php
new file mode 100644
index 00000000..9d3417bd
--- /dev/null
+++ b/www/wiki/skins/chameleon/tests/phpunit/Unit/Components/GenericComponentTestCase.php
@@ -0,0 +1,345 @@
+<?php
+/**
+ * This file is part of the MediaWiki skin Chameleon.
+ *
+ * @copyright 2013 - 2014, Stephan Gambke
+ * @license GNU General Public License, version 3 (or any later version)
+ *
+ * The Chameleon skin is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * The Chameleon skin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @file
+ * @ingroup Skins
+ */
+
+namespace Skins\Chameleon\Tests\Unit\Components;
+
+use DOMDocument;
+use DOMXPath;
+use Skins\Chameleon\Components\Component;
+use Skins\Chameleon\Tests\Util\DocumentElementFinder;
+use Skins\Chameleon\Tests\Util\MockupFactory;
+use Skins\Chameleon\Tests\Util\XmlFileProvider;
+
+/**
+ * @coversDefaultClass \Skins\Chameleon\Components\Component
+ * @covers ::<private>
+ * @covers ::<protected>
+ *
+ * @group skins-chameleon
+ * @group mediawiki-databaseless
+ *
+ * @author Stephan Gambke
+ * @since 1.0
+ * @ingroup Skins
+ * @ingroup Test
+ */
+class GenericComponentTestCase extends \PHPUnit_Framework_TestCase {
+
+ private $successColor = '';
+ private $testObject;
+ protected $classUnderTest;
+ protected $componentUnderTest;
+
+ private static $lastValidatorCallTime = 0;
+
+ /**
+ * @covers ::__construct
+ */
+ public function testCanConstruct() {
+
+ /** @var $instance Component */
+ $instance = $this->getTestObject();
+
+ $this->assertInstanceOf(
+ $this->classUnderTest,
+ $instance
+ );
+
+ $this->assertEquals( 0, $instance->getIndent() );
+ $this->assertNull( $instance->getDomElement() );
+ }
+
+ /**
+ * @covers ::getHtml
+ */
+ public function testGetHtml_withEmptyElement() {
+
+ /** @var $instance Component */
+ $instance = $this->getTestObject();
+ $this->assertValidHTML( $instance->getHtml() );
+ }
+
+ /**
+ * @covers ::getHtml
+ * @dataProvider domElementProviderFromSyntheticLayoutFiles
+ *
+ * @param \DOMElement $domElement
+ */
+ public function testGetHtml_OnSyntheticLayoutXml( \DOMElement $domElement ) {
+
+ /** @var $instance Component */
+ $instance = $this->getTestObject( $domElement );
+ $this->assertValidHTML( $instance->getHtml() );
+ }
+
+ /**
+ * @covers ::getHtml
+ * @dataProvider domElementProviderFromDeployedLayoutFiles
+ */
+ public function testGetHtml_OnDeployedLayoutXml( $domElement ) {
+
+ if ( $domElement === null ) {
+ $this->assertTrue( true );
+ return;
+ }
+
+ /** @var $instance Component */
+ $instance = $this->getTestObject( $domElement );
+
+ $this->assertValidHTML( $instance->getHtml() );
+ }
+
+ public function getTestObject( \DOMElement $domElement = null ) {
+ if ( $this->testObject === null ) {
+ $chameleonTemplate = $this->getChameleonSkinTemplateStub();
+ $this->testObject = new $this->classUnderTest ( $chameleonTemplate, $domElement );
+ }
+ return $this->testObject;
+ }
+
+ public function domElementProviderFromSyntheticLayoutFiles() {
+ $file = __DIR__ . '/../../Fixture/' . $this->getNameOfComponentUnderTest() . '.xml';
+ $provider = array_chunk( $this->getDomElementsFromFile( $file ), 1 );
+ return $provider;
+ }
+
+ public function domElementProviderFromDeployedLayoutFiles() {
+
+ $xmlFileProvider = new XmlFileProvider( __DIR__ . '/../../../../layouts' );
+ $files = $xmlFileProvider->getFiles();
+
+ $elements = array();
+ foreach ( $files as $file ) {
+ $elements = array_merge( $elements, $this->getDomElementsFromFile( $file ) );
+ }
+
+ if ( count( $elements ) === 0 ) {
+ $elements[ ] = null;
+ }
+
+ $provider = array_chunk( $elements, 1 );
+
+ return $provider;
+ }
+
+ protected function getDomElementsFromFile( $file ) {
+ $elementFinder = new DocumentElementFinder( $file );
+ $nameParts = array_values( explode( '\\', $this->getNameOfComponentUnderTest() ) );
+ $componentName = end( $nameParts );
+ return $elementFinder->getComponentsByTypeAttribute( $componentName );
+ }
+
+ protected static function loadXML( $fragment, $isHtml = true ) {
+
+ if ( $isHtml ) {
+ $fragment = self::wrapHtmlFragment( $fragment );
+ }
+
+ $doc = new DOMDocument();
+ $doc->preserveWhiteSpace = false;
+
+ if ( $isHtml === true ) {
+ libxml_use_internal_errors( true );
+ $result = $doc->loadHTML( $fragment );
+ libxml_use_internal_errors( false );
+ } else {
+ $result = $doc->loadXML( $fragment );
+ }
+
+ if ( $result === true ) {
+ return $doc;
+ } else {
+ return false;
+ }
+
+ }
+
+ protected static function wrapHtmlFragment( $fragment ) {
+ return '<!DOCTYPE html><html><head><meta charset="utf-8" /><title>SomeTitle</title></head><body>' . $fragment . '</body></html>';
+ }
+
+ /**
+ * Evaluate an HTML or XML string and assert its structure and/or contents.
+ *
+ * @todo: Currently only supports 'tag' and 'class'
+ *
+ * The first argument ($matcher) is an associative array that specifies the
+ * match criteria for the assertion:
+ *
+ * - `id` : the node with the given id attribute must match the
+ * corresponding value.
+ * - `tag` : the node type must match the corresponding value.
+ * - `attributes` : a hash. The node's attributes must match the
+ * corresponding values in the hash.
+ * - `class` : The node's class attribute must contain the given
+ * value.
+ * - `content` : The text content must match the given value.
+ * - `parent` : a hash. The node's parent must match the
+ * corresponding hash.
+ * - `child` : a hash. At least one of the node's immediate children
+ * must meet the criteria described by the hash.
+ * - `ancestor` : a hash. At least one of the node's ancestors must
+ * meet the criteria described by the hash.
+ * - `descendant` : a hash. At least one of the node's descendants must
+ * meet the criteria described by the hash.
+ * - `children` : a hash, for counting children of a node.
+ * Accepts the keys:
+ * - `count` : a number which must equal the number of children
+ * that match
+ * - `less_than` : the number of matching children must be greater
+ * than this number
+ * - `greater_than` : the number of matching children must be less than
+ * this number
+ * - `only` : another hash consisting of the keys to use to match
+ * on the children, and only matching children will be
+ * counted
+ *
+ * @param array $matcher
+ * @param string $actual
+ * @param string $message
+ * @param bool $isHtml
+ */
+ public static function assertTag( $matcher, $actual, $message = 'Failed asserting that the given fragment contained the described node.', $isHtml = true ) {
+
+ $doc = self::loadXML( $actual, $isHtml );
+
+ if ( $doc === false ) {
+ self::fail( $message );
+ }
+
+ $query = '//';
+
+ if ( array_key_exists( 'tag', $matcher ) ) {
+ $query .= strtolower( $matcher[ 'tag' ] );
+ unset( $matcher[ 'tag' ] );
+ } else {
+ $query .= '*';
+ }
+
+ if ( array_key_exists( 'class', $matcher ) ) {
+ $query .= '[contains(concat(" ", normalize-space(@class), " "), " ' . $matcher[ 'class' ] . ' ")]';
+ unset( $matcher[ 'class' ] );
+ }
+
+ if ( count( $matcher ) > 0 ) {
+ trigger_error( 'Found unsupported matcher tags: ' . implode( ', ', array_keys( $matcher ) ), E_USER_WARNING );
+ }
+
+ $xpath = new DOMXPath( $doc );
+ $entries = $xpath->query( $query );
+
+ self::assertGreaterThan( 0, $entries->length, $message );
+
+ }
+
+ /**
+ * Asserts that $actual is a valid HTML fragment
+ *
+ * @todo Put this whole stuff in a \PHPUnit_Framework_Constraint and just call assertThat
+ *
+ * @param $actual
+ * @param string $message
+ */
+ public function assertValidHTML( $actual, $message = 'HTML text is not valid. ' ) {
+
+ if ( !USE_EXTERNAL_HTML_VALIDATOR ) {
+
+ $doc = $this->loadXML( $actual, true );
+ $this->assertNotFalse( $doc, $message );
+
+ return;
+ }
+
+ $actual = $this->wrapHtmlFragment( $actual );
+
+ $curlVersion = curl_version();
+
+ // cURL
+ $curl = curl_init();
+
+ curl_setopt_array( $curl, array(
+ CURLOPT_CONNECTTIMEOUT => 1,
+ CURLOPT_URL => 'http://validator.w3.org/check',
+ CURLOPT_USERAGENT => 'cURL ' . $curlVersion[ 'version' ],
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => array(
+ 'output' => 'json',
+ 'fragment' => $actual,
+ ),
+ ) );
+
+ @time_sleep_until( self::$lastValidatorCallTime + 1 );
+ self::$lastValidatorCallTime = time();
+
+ $response = curl_exec( $curl );
+ $curlInfo = curl_getinfo( $curl );
+
+ curl_close( $curl );
+
+ if ( $response === false ) {
+ $this->markTestIncomplete( 'Could not connect to validation service.' );
+ }
+
+ if ( $curlInfo[ 'http_code' ] != '200' ) {
+ $this->markTestIncomplete( 'Error connecting to validation service. HTTP ' . $curlInfo[ 'http_code' ] );
+ }
+
+ $response = json_decode( $response, true );
+
+ if ( $response === null ) {
+ $this->markTestIncomplete( 'Validation service returned an invalid response (invalid JSON): ' . $response );
+ }
+
+ // fail if errors or warnings
+ if ( array_key_exists( 'messages', $response ) ) {
+
+ foreach ( $response[ 'messages' ] as $responseMessage ) {
+
+ if ( $responseMessage[ 'type' ] === 'error' || $responseMessage[ 'type' ] === 'warning' ) {
+ $this->fail( $message . ucfirst( $response[ 'messages' ][ 0 ][ 'type' ] ) . ': ' . $response[ 'messages' ][ 0 ][ 'message' ] );
+ }
+
+ }
+ }
+
+ // valid
+ $this->successColor = 'bg-green,fg-black';
+ $this->assertTrue( true );
+
+ }
+
+ public function getChameleonSkinTemplateStub() {
+ return MockupFactory::makeFactory( $this )->getChameleonSkinTemplateStub();
+ }
+
+ public function getSuccessColor() {
+ return $this->successColor;
+ }
+
+ public function getNameOfComponentUnderTest() {
+ return str_replace( 'Skins\\Chameleon\\Components\\', '', get_class( $this->getTestObject() ) );
+ }
+
+}