MediaWiki master
SpecialJavaScriptTest.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Specials;
22
31
37
38 public function __construct() {
39 parent::__construct( 'JavaScriptTest' );
40 }
41
42 public function execute( $par ) {
43 $this->getOutput()->disable();
44
45 if ( $par === 'qunit/export' ) {
46 // Send the JavaScript payload.
47 $this->exportJS();
48 } elseif ( $par === null || $par === '' || $par === 'qunit' || $par === 'qunit/plain' ) {
49 // Render the page
50 // (Support "/qunit" and "/qunit/plain" for backwards-compatibility)
51 $this->renderPage();
52 } else {
53 wfHttpError( 404, 'Unknown action', "Unknown action \"$par\"." );
54 }
55 }
56
62 private function exportJS() {
63 $out = $this->getOutput();
64 $req = $this->getContext()->getRequest();
65 $rl = $out->getResourceLoader();
66
67 // Allow framing (disabling wgBreakFrames). Otherwise, mediawiki.page.ready
68 // will close this tab when running from CLI using karma-qunit.
69 $out->setPreventClickjacking( false );
70
71 $query = [
72 'lang' => 'qqx',
73 'skin' => 'fallback',
74 'debug' => $req->getRawVal( 'debug' ),
75 'target' => 'test',
76 ];
77 $embedContext = new RL\Context( $rl, new FauxRequest( $query ) );
78 $query['only'] = 'scripts';
79 $startupContext = new RL\Context( $rl, new FauxRequest( $query ) );
80
81 $modules = $rl->getTestSuiteModuleNames();
82 $component = $req->getRawVal( 'component' );
83 if ( $component ) {
84 $module = 'test.' . $component;
85 if ( !in_array( 'test.' . $component, $modules ) ) {
87 404,
88 'Unknown test module',
89 "'$module' is not a defined test module. "
90 . 'Register one via the QUnitTestModules attribute in extension.json.'
91 );
92 return;
93 }
94 $modules = [ 'test.' . $component ];
95 }
96
97 // Disable module storage.
98 // The unit test for mw.loader.store will enable it (with a mock timers).
99 $config = new MultiConfig( [
100 new HashConfig( [ MainConfigNames::ResourceLoaderStorageEnabled => false ] ),
101 $rl->getConfig(),
102 ] );
103
104 // The below is essentially a pure-javascript version of OutputPage::headElement().
105 $startupModule = $rl->getModule( 'startup' );
106 $startupModule->setConfig( $config );
107 $code = $rl->makeModuleResponse( $startupContext, [ 'startup' => $startupModule ] );
108 // The following has to be deferred via RLQ because the startup module is asynchronous.
109 $code .= ResourceLoader::makeLoaderConditionalScript(
110 // Embed page-specific mw.config variables.
111 //
112 // For compatibility with older tests, these will come from the user
113 // action "viewing Special:JavaScripTest".
114 //
115 // This is deprecated since MediaWiki 1.25 and slowly being phased out in favour of:
116 // 1. tests explicitly mocking the configuration they depend on.
117 // 2. tests explicitly skipping or not loading code that is only meant
118 // for real page views (e.g. not loading as dependency, or using a QUnit
119 // conditional).
120 //
121 // See https://phabricator.wikimedia.org/T89434.
122 // Keep a select few that are commonly referenced.
123 ResourceLoader::makeConfigSetScript( [
124 // used by mediawiki.util
125 'wgPageName' => 'Special:Badtitle/JavaScriptTest',
126 // used as input for mw.Title
127 'wgRelevantPageName' => 'Special:Badtitle/JavaScriptTest',
128 ] )
129 // Embed private modules as they're not allowed to be loaded dynamically
130 . $rl->makeModuleResponse( $embedContext, [
131 'user.options' => $rl->getModule( 'user.options' ),
132 ] )
133 // Load all the test modules
134 . Html::encodeJsCall( 'mw.loader.load', [ $modules ] )
135 );
136 $encModules = Html::encodeJsVar( $modules );
137 $code .= ResourceLoader::makeInlineCodeWithModule( 'mediawiki.base', <<<JAVASCRIPT
138 // Wait for each module individually, so that partial failures wont break the page
139 // completely by rejecting the promise before all/ any modules are loaded.
140 var promises = $encModules.map( function( module ) {
141 return mw.loader.using( module ).promise();
142 } );
143 Promise.allSettled( promises ).then( QUnit.start );
144JAVASCRIPT
145 );
146
147 header( 'Content-Type: text/javascript; charset=utf-8' );
148 header( 'Cache-Control: private, no-cache, must-revalidate' );
149 echo $code;
150 }
151
152 private function renderPage() {
153 $basePath = $this->getConfig()->get( MainConfigNames::ResourceBasePath );
154 $headHtml = implode( "\n", [
155 Html::linkedStyle( "$basePath/resources/lib/qunitjs/qunit.css" ),
156 Html::linkedStyle( "$basePath/resources/src/qunitjs/qunit-local.css" ),
157 ] );
158
159 $introHtml = $this->msg( 'javascripttest-qunit-intro' )
160 ->params( 'https://www.mediawiki.org/wiki/Manual:JavaScript_unit_testing' )
161 ->parseAsBlock();
162
163 $scriptUrl = $this->getPageTitle( 'qunit/export' )->getFullURL( [
164 'debug' => (string)ResourceLoader::inDebugMode(),
165 ] );
166 $script = implode( "\n", [
167 Html::linkedScript( "$basePath/resources/lib/qunitjs/qunit.js" ),
168 Html::inlineScript( 'QUnit.config.autostart = false;' ),
169 Html::linkedScript( $scriptUrl ),
170 ] );
171
172 header( 'Content-Type: text/html; charset=utf-8' );
173 echo <<<HTML
174<!DOCTYPE html>
175<title>QUnit</title>
176$headHtml
177$introHtml
178<div id="qunit"></div>
179<div id="qunit-fixture"></div>
180$script
181HTML;
182 }
183
184 protected function getGroupName() {
185 return 'other';
186 }
187}
188
190class_alias( SpecialJavaScriptTest::class, 'SpecialJavaScriptTest' );
wfHttpError( $code, $label, $desc)
Provide a simple HTTP error.
A Config instance which stores all settings as a member variable.
Provides a fallback sequence for Config objects.
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
A class containing constants representing the names of configuration variables.
const ResourceBasePath
Name constant for the ResourceBasePath setting, for use with Config::get()
const ResourceLoaderStorageEnabled
Name constant for the ResourceLoaderStorageEnabled setting, for use with Config::get()
WebRequest clone which takes values from a provided array.
Context object that contains information about the state of a specific ResourceLoader web request.
Definition Context.php:45
ResourceLoader is a loading system for JavaScript and CSS resources.
Parent class for all special pages.
getPageTitle( $subpage=false)
Get a self-referential title object.
getConfig()
Shortcut to get main config object.
getContext()
Gets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
execute( $par)
Default execute method Checks user permissions.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...