diff options
Diffstat (limited to 'includes/specials/SpecialJavaScriptTest.php')
-rw-r--r-- | includes/specials/SpecialJavaScriptTest.php | 248 |
1 files changed, 172 insertions, 76 deletions
diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php index 0efebb3e..7d745a50 100644 --- a/includes/specials/SpecialJavaScriptTest.php +++ b/includes/specials/SpecialJavaScriptTest.php @@ -26,12 +26,10 @@ */ class SpecialJavaScriptTest extends SpecialPage { /** - * @var array Mapping of framework ids and their initilizer methods - * in this class. If a framework is requested but not in this array, - * the 'unknownframework' error is served. + * @var array Supported frameworks. */ private static $frameworks = array( - 'qunit' => 'initQUnitTesting', + 'qunit', ); public function __construct() { @@ -44,43 +42,70 @@ class SpecialJavaScriptTest extends SpecialPage { $this->setHeaders(); $out->disallowUserJs(); - $out->addModules( 'mediawiki.special.javaScriptTest' ); - - // Determine framework - $pars = explode( '/', $par ); - $framework = strtolower( $pars[0] ); - - // No framework specified - if ( $par == '' ) { + if ( $par === null ) { + // No framework specified + $out->setStatusCode( 404 ); $out->setPageTitle( $this->msg( 'javascripttest' ) ); - $summary = $this->wrapSummaryHtml( - $this->msg( 'javascripttest-pagetext-noframework' )->escaped() . - $this->getFrameworkListHtml(), - 'noframework' + $out->addHTML( + $this->msg( 'javascripttest-pagetext-noframework' )->parseAsBlock() + . $this->getFrameworkListHtml() ); - $out->addHtml( $summary ); - } elseif ( isset( self::$frameworks[$framework] ) ) { - // Matched! Display proper title and initialize the framework - $out->setPageTitle( $this->msg( - 'javascripttest-title', - // Messages: javascripttest-qunit-name - $this->msg( "javascripttest-$framework-name" )->plain() - ) ); - $out->setSubtitle( $this->msg( 'javascripttest-backlink' ) - ->rawParams( Linker::linkKnown( $this->getPageTitle() ) ) ); - $this->{self::$frameworks[$framework]}(); - } else { - // Framework not found, display error - $out->setPageTitle( $this->msg( 'javascripttest' ) ); - $summary = $this->wrapSummaryHtml( - '<p class="error">' . - $this->msg( 'javascripttest-pagetext-unknownframework', $par )->escaped() . - '</p>' . - $this->getFrameworkListHtml(), - 'unknownframework' + return; + } + + // Determine framework and mode + $pars = explode( '/', $par, 2 ); + + $framework = $pars[0]; + if ( !in_array( $framework, self::$frameworks ) ) { + // Framework not found + $out->setStatusCode( 404 ); + $out->addHTML( + '<div class="error">' + . $this->msg( 'javascripttest-pagetext-unknownframework' ) + ->plaintextParams( $par )->parseAsBlock() + . '</div>' + . $this->getFrameworkListHtml() ); - $out->addHtml( $summary ); + return; } + + // This special page is disabled by default ($wgEnableJavaScriptTest), and contains + // no sensitive data. In order to allow TestSwarm to embed it into a test client window, + // we need to allow iframing of this page. + $out->allowClickjacking(); + $out->setSubtitle( + $this->msg( 'javascripttest-backlink' ) + ->rawParams( Linker::linkKnown( $this->getPageTitle() ) ) + ); + + // Custom actions + if ( isset( $pars[1] ) ) { + $action = $pars[1]; + if ( !in_array( $action, array( 'export', 'plain' ) ) ) { + $out->setStatusCode( 404 ); + $out->addHTML( + '<div class="error">' + . $this->msg( 'javascripttest-pagetext-unknownaction' ) + ->plaintextParams( $action )->parseAsBlock() + . '</div>' + ); + return; + } + $method = $action . ucfirst( $framework ); + $this->$method(); + return; + } + + $out->addModules( 'mediawiki.special.javaScriptTest' ); + + $method = 'view' . ucfirst( $framework ); + $this->$method(); + $out->setPageTitle( $this->msg( + 'javascripttest-title', + // Messages: javascripttest-qunit-name + $this->msg( "javascripttest-$framework-name" )->plain() + ) ); } /** @@ -91,7 +116,7 @@ class SpecialJavaScriptTest extends SpecialPage { */ private function getFrameworkListHtml() { $list = '<ul>'; - foreach ( self::$frameworks as $framework => $initFn ) { + foreach ( self::$frameworks as $framework ) { $list .= Html::rawElement( 'li', array(), @@ -109,60 +134,35 @@ class SpecialJavaScriptTest extends SpecialPage { } /** - * Function to wrap the summary. - * It must be given a valid state as a second parameter or an exception will - * be thrown. - * @param string $html The raw HTML. - * @param string $state State, one of 'noframework', 'unknownframework' or 'frameworkfound' - * @throws MWException - * @return string + * Wrap HTML contents in a summary container. + * + * @param string $html HTML contents to be wrapped + * @return string HTML */ - private function wrapSummaryHtml( $html, $state ) { - $validStates = array( 'noframework', 'unknownframework', 'frameworkfound' ); - - if ( !in_array( $state, $validStates ) ) { - throw new MWException( __METHOD__ - . ' given an invalid state. Must be one of "' - . join( '", "', $validStates ) . '".' - ); - } - - return "<div id=\"mw-javascripttest-summary\" class=\"mw-javascripttest-$state\">$html</div>"; + private function wrapSummaryHtml( $html ) { + return "<div id=\"mw-javascripttest-summary\">$html</div>"; } /** - * Initialize the page for QUnit. + * Run the test suite on the Special page. + * + * Rendered by OutputPage and Skin. */ - private function initQUnitTesting() { + private function viewQUnit() { $out = $this->getOutput(); $testConfig = $this->getConfig()->get( 'JavaScriptTestConfig' ); - $out->addModules( 'test.mediawiki.qunit.testrunner' ); - $qunitTestModules = $out->getResourceLoader()->getTestModuleNames( 'qunit' ); - $out->addModules( $qunitTestModules ); + $modules = $out->getResourceLoader()->getTestModuleNames( 'qunit' ); $summary = $this->msg( 'javascripttest-qunit-intro' ) ->params( $testConfig['qunit']['documentation'] ) ->parseAsBlock(); - $header = $this->msg( 'javascripttest-qunit-heading' )->escaped(); - $userDir = $this->getLanguage()->getDir(); $baseHtml = <<<HTML <div class="mw-content-ltr"> -<div id="qunit-header"><span dir="$userDir">$header</span></div> -<div id="qunit-banner"></div> -<div id="qunit-testrunner-toolbar"></div> -<div id="qunit-userAgent"></div> -<ol id="qunit-tests"></ol> -<div id="qunit-fixture">test markup, will be hidden</div> +<div id="qunit"></div> </div> HTML; - $out->addHtml( $this->wrapSummaryHtml( $summary, 'frameworkfound' ) . $baseHtml ); - - // This special page is disabled by default ($wgEnableJavaScriptTest), and contains - // no sensitive data. In order to allow TestSwarm to embed it into a test client window, - // we need to allow iframing of this page. - $out->allowClickjacking(); // Used in ./tests/qunit/data/testrunner.js, see also documentation of // $wgJavaScriptTestConfig in DefaultSettings.php @@ -170,6 +170,102 @@ HTML; 'QUnitTestSwarmInjectJSPath', $testConfig['qunit']['testswarm-injectjs'] ); + + $out->addHtml( $this->wrapSummaryHtml( $summary ) . $baseHtml ); + + // The testrunner configures QUnit and essentially depends on it. However, test suites + // are reusable in environments that preload QUnit (or a compatibility interface to + // another framework). Therefore we have to load it ourselves. + $out->addHtml( Html::inlineScript( + ResourceLoader::makeLoaderConditionalScript( + Xml::encodeJsCall( 'mw.loader.using', array( + array( 'jquery.qunit', 'jquery.qunit.completenessTest' ), + new XmlJsCode( + 'function () {' . Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) . '}' + ) + ) ) + ) + ) ); + } + + /** + * Generate self-sufficient JavaScript payload to run the tests elsewhere. + * + * Includes startup module to request modules from ResourceLoader. + * + * Note: This modifies the registry to replace 'jquery.qunit' with an + * empty module to allow external environment to preload QUnit with any + * neccecary framework adapters (e.g. Karma). Loading it again would + * re-define QUnit and dereference event handlers from Karma. + */ + private function exportQUnit() { + $out = $this->getOutput(); + + $out->disable(); + + $rl = $out->getResourceLoader(); + + $query = array( + 'lang' => $this->getLanguage()->getCode(), + 'skin' => $this->getSkin()->getSkinName(), + 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false', + ); + $embedContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) ); + $query['only'] = 'scripts'; + $startupContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) ); + + $modules = $rl->getTestModuleNames( 'qunit' ); + + // The below is essentially a pure-javascript version of OutputPage::getHeadScripts. + $startup = $rl->makeModuleResponse( $startupContext, array( + 'startup' => $rl->getModule( 'startup' ), + ) ); + // Embed page-specific mw.config variables. + // The current Special page shouldn't be relevant to tests, but various modules (which + // are loaded before the test suites), reference mw.config while initialising. + $code = ResourceLoader::makeConfigSetScript( $out->getJSVars() ); + // Embed private modules as they're not allowed to be loaded dynamically + $code .= $rl->makeModuleResponse( $embedContext, array( + 'user.options' => $rl->getModule( 'user.options' ), + 'user.tokens' => $rl->getModule( 'user.tokens' ), + ) ); + $code .= Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ); + + header( 'Content-Type: text/javascript; charset=utf-8' ); + header( 'Cache-Control: private, no-cache, must-revalidate' ); + header( 'Pragma: no-cache' ); + echo $startup; + echo "\n"; + // Note: The following has to be wrapped in a script tag because the startup module also + // writes a script tag (the one loading mediawiki.js). Script tags are synchronous, block + // each other, and run in order. But they don't nest. The code appended after the startup + // module runs before the added script tag is parsed and executed. + echo Xml::encodeJsCall( 'document.write', array( Html::inlineScript( $code ) ) ); + } + + private function plainQUnit() { + $out = $this->getOutput(); + $out->disable(); + + $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array( + 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false', + ) ); + + $styles = $out->makeResourceLoaderLink( 'jquery.qunit', ResourceLoaderModule::TYPE_STYLES, false ); + // Use 'raw' since this is a plain HTML page without ResourceLoader + $scripts = $out->makeResourceLoaderLink( 'jquery.qunit', ResourceLoaderModule::TYPE_SCRIPTS, false, array( 'raw' => 'true' ) ); + + $head = trim( $styles['html'] . $scripts['html'] ); + $html = <<<HTML +<!DOCTYPE html> +<title>QUnit</title> +$head +<div id="qunit"></div> +HTML; + $html .= "\n" . Html::linkedScript( $url ); + + header( 'Content-Type: text/html; charset=utf-8' ); + echo $html; } /** @@ -183,7 +279,7 @@ HTML; return self::prefixSearchArray( $search, $limit, - array_keys( self::$frameworks ) + self::$frameworks ); } |