MediaWiki REL1_35
SpecialJavaScriptTest.php
Go to the documentation of this file.
1<?php
28
29 public function __construct() {
30 parent::__construct( 'JavaScriptTest' );
31 }
32
33 public function execute( $par ) {
34 $this->getOutput()->disable();
35
36 if ( $par === 'qunit/export' ) {
37 // Send the JavaScript payload.
38 $this->exportJS();
39 } elseif ( $par === null || $par === '' || $par === 'qunit' || $par === 'qunit/plain' ) {
40 // Render the page
41 // (Support "/qunit" and "/qunit/plain" for backwards-compatibility)
42 $this->renderPage();
43 } else {
44 wfHttpError( 404, 'Unknown action', "Unknown action \"$par\"." );
45 }
46 }
47
53 private function exportJS() {
54 $out = $this->getOutput();
55 $rl = $out->getResourceLoader();
56
57 // Allow framing (disabling wgBreakFrames). Otherwise, mediawiki.page.startup.js
58 // will close this tab when run from CLI using karma-qunit.
59 $out->allowClickjacking();
60
61 $query = [
62 'lang' => 'qqx',
63 'skin' => 'fallback',
64 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
65 'target' => 'test',
66 ];
67 $embedContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) );
68 $query['only'] = 'scripts';
69 $startupContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) );
70
71 $modules = $rl->getTestSuiteModuleNames();
72
73 // Disable module storage.
74 // The unit test for mw.loader.store will enable it (with a mock timers).
75 $config = new MultiConfig( [
76 new HashConfig( [ 'ResourceLoaderStorageEnabled' => false ] ),
77 $rl->getConfig(),
78 ] );
79
80 // Disable autostart because we load modules asynchronously. By default, QUnit would start
81 // at domready when there are no tests loaded and also fire 'QUnit.done' which then instructs
82 // Karma to exit the browser process before the tests even finished loading.
83 $qunitConfig = 'QUnit.config.autostart = false;'
84 . 'if (window.__karma__) {'
85 // karma-qunit's use of autostart=false and QUnit.start conflicts with ours.
86 // Hack around this by replacing 'karma.loaded' with a no-op and perform its duty of calling
87 // `__karma__.start()` ourselves. See <https://github.com/karma-runner/karma-qunit/issues/27>.
88 . 'window.__karma__.loaded = function () {};'
89 . '}';
90
91 // The below is essentially a pure-javascript version of OutputPage::headElement().
92 $startupModule = $rl->getModule( 'startup' );
93 $startupModule->setConfig( $config );
94 $code = $rl->makeModuleResponse( $startupContext, [ 'startup' => $startupModule ] );
95 // The following has to be deferred via RLQ because the startup module is asynchronous.
97 // Embed page-specific mw.config variables.
98 // The current Special page shouldn't be relevant to tests, but various modules (which
99 // are loaded before the test suites), reference mw.config while initialising.
100 ResourceLoader::makeConfigSetScript( $out->getJSVars() )
101 // Embed private modules as they're not allowed to be loaded dynamically
102 . $rl->makeModuleResponse( $embedContext, [
103 'user.options' => $rl->getModule( 'user.options' ),
104 ] )
105 // Load all the test suites
106 . Xml::encodeJsCall( 'mw.loader.load', [ $modules ] )
107 );
108 $encModules = Xml::encodeJsVar( $modules );
109 $code .= ResourceLoader::makeInlineCodeWithModule( 'mediawiki.base', <<<JAVASCRIPT
110 var start = window.__karma__ ? window.__karma__.start : QUnit.start;
111 mw.loader.using( $encModules ).always( start );
112 mw.trackSubscribe( 'resourceloader.exception', function ( topic, err ) {
113 // Things like "dependency missing" or "unknown module".
114 // Re-throw so that they are reported as global exceptions by QUnit and Karma.
115 setTimeout( function () {
116 throw err;
117 } );
118 } );
119JAVASCRIPT
120 );
121
122 header( 'Content-Type: text/javascript; charset=utf-8' );
123 header( 'Cache-Control: private, no-cache, must-revalidate' );
124 header( 'Pragma: no-cache' );
125 echo $qunitConfig;
126 echo $code;
127 }
128
129 private function renderPage() {
130 $basePath = $this->getConfig()->get( 'ResourceBasePath' );
131 $headHtml = implode( "\n", [
132 Html::linkedScript( "$basePath/resources/lib/qunitjs/qunit.js" ),
133 Html::linkedStyle( "$basePath/resources/lib/qunitjs/qunit.css" ),
134 Html::linkedStyle( "$basePath/resources/src/qunitjs/qunit-local.css" ),
135 ] );
136
137 $introHtml = $this->msg( 'javascripttest-qunit-intro' )
138 ->params( 'https://www.mediawiki.org/wiki/Manual:JavaScript_unit_testing' )
139 ->parseAsBlock();
140
141 $scriptUrl = $this->getPageTitle( 'qunit/export' )->getFullURL( [
142 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
143 ] );
144 $script = Html::linkedScript( $scriptUrl );
145
146 header( 'Content-Type: text/html; charset=utf-8' );
147 echo <<<HTML
148<!DOCTYPE html>
149<title>QUnit</title>
150$headHtml
151$introHtml
152<div id="qunit"></div>
153$script
154HTML;
155 }
156
157 protected function getGroupName() {
158 return 'other';
159 }
160}
wfHttpError( $code, $label, $desc)
Provide a simple HTTP error.
$basePath
Definition addSite.php:5
WebRequest clone which takes values from a provided array.
A Config instance which stores all settings as a member variable.
Provides a fallback sequence for Config objects.
Context object that contains information about the state of a specific ResourceLoader web request.
static makeLoaderConditionalScript( $script)
Wrap JavaScript code to run after the startup module.
static inDebugMode()
Determine whether debug mode is on.
static makeConfigSetScript(array $configuration)
Return JS code which will set the MediaWiki configuration array to the given value.
static makeInlineCodeWithModule( $modules, $script)
Wrap JavaScript code to run after a required module.
execute( $par)
Default execute method Checks user permissions.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
exportJS()
Send the standalone JavaScript payload.
Parent class for all special pages.
getOutput()
Get the OutputPage being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getPageTitle( $subpage=false)
Get a self-referential title object.