Index: branches/JSTesting/languages/messages/MessagesEn.php |
— | — | @@ -392,6 +392,7 @@ |
393 | 393 | 'Filepath' => array( 'FilePath' ), |
394 | 394 | 'Import' => array( 'Import' ), |
395 | 395 | 'Invalidateemail' => array( 'InvalidateEmail' ), |
| 396 | + 'JavaScriptTest' => array( 'JavaScriptTest' ), |
396 | 397 | 'BlockList' => array( 'BlockList', 'ListBlocks', 'IPBlockList' ), |
397 | 398 | 'LinkSearch' => array( 'LinkSearch' ), |
398 | 399 | 'Listadmins' => array( 'ListAdmins' ), |
— | — | @@ -2957,6 +2958,18 @@ |
2958 | 2959 | 'undelete-show-file-confirm' => 'Are you sure you want to view the deleted revision of the file "<nowiki>$1</nowiki>" from $2 at $3?', |
2959 | 2960 | 'undelete-show-file-submit' => 'Yes', |
2960 | 2961 | |
| 2962 | +# JavaScriptTest |
| 2963 | +'javascripttest' => 'JavaScript Test', |
| 2964 | +'javascripttest-disabled' => 'This function is disabled.', |
| 2965 | +'javascripttest-title' => 'Running $1 tests', |
| 2966 | +'javascripttest-pagetext-noframework' => 'This page is reserved for running javascript tests.', |
| 2967 | +'javascripttest-pagetext-unknownframework' => 'Unknown framework "$1".', |
| 2968 | +'javascripttest-pagetext-frameworks' => 'Please choose one of the following frameworks: $1', |
| 2969 | +'javascripttest-pagetext-skins' => 'Available skins: ', |
| 2970 | +'javascripttest-qunit-name' => 'QUnit', // Ignore, do not translate |
| 2971 | +'javascripttest-qunit-intro' => 'See [$1 testing documentation] on mediawiki.org.', |
| 2972 | +'javascripttest-qunit-heading' => 'MediaWiki JavaScript Test Suite', // Optional, translate if needed |
| 2973 | + |
2961 | 2974 | # Namespace form on various pages |
2962 | 2975 | 'namespace' => 'Namespace:', |
2963 | 2976 | 'invert' => 'Invert selection', |
Index: branches/JSTesting/languages/messages/MessagesQqq.php |
— | — | @@ -2613,6 +2613,16 @@ |
2614 | 2614 | {{identical|Are you sure you want to view the deleted revision of the file...}}', |
2615 | 2615 | 'undelete-show-file-submit' => '{{Identical|Yes}}', |
2616 | 2616 | |
| 2617 | +# JavaScriptTest |
| 2618 | +'javascripttest' => 'Title of the special page', |
| 2619 | +'javascripttest-disabled' => '{{Identical|Function disabled}}.', |
| 2620 | +'javascripttest-title' => 'Title of the special page when running a test suite. $1 is the name of the framework.', |
| 2621 | +'javascripttest-pagetext-unknownframework' => 'Error message when given framework id is not found. $1 is the if of the framework.', |
| 2622 | +'javascripttest-pagetext-frameworks' => '$1 is the if of the framework.', |
| 2623 | +'javascripttest-qunit-name' => '{{Ignore}}', |
| 2624 | +'javascripttest-qunit-intro' => '$1 is the configured url to the documentation.', |
| 2625 | +'javascripttest-qunit-heading' => '{{Optional}}', |
| 2626 | + |
2617 | 2627 | # Namespace form on various pages |
2618 | 2628 | 'namespace' => 'This message is located at [[Special:Contributions]].', |
2619 | 2629 | 'invert' => 'Displayed in [[Special:RecentChanges|RecentChanges]], [[Special:RecentChangesLinked|RecentChangesLinked]] and [[Special:Watchlist|Watchlist]] |
Index: branches/JSTesting/resources/Resources.php |
— | — | @@ -666,6 +666,13 @@ |
667 | 667 | 'mediawiki.special.undelete' => array( |
668 | 668 | 'scripts' => 'resources/mediawiki.special/mediawiki.special.undelete.js', |
669 | 669 | ), |
| 670 | + 'mediawiki.special.javaScriptTest' => array( |
| 671 | + 'scripts' => 'resources/mediawiki.special/mediawiki.special.javaScriptTest.js', |
| 672 | + 'messages' => array_merge( Skin::getSkinNameMessages(), array( |
| 673 | + 'javascripttest-pagetext-skins' |
| 674 | + ) ), |
| 675 | + 'dependencies' => array( 'jquery.qunit' ), |
| 676 | + ), |
670 | 677 | 'mediawiki.special.movePage' => array( |
671 | 678 | 'scripts' => 'resources/mediawiki.special/mediawiki.special.movePage.js', |
672 | 679 | 'dependencies' => 'jquery.byteLimit', |
— | — | @@ -689,6 +696,17 @@ |
690 | 697 | 'dependencies' => array( 'mediawiki.libs.jpegmeta' ), |
691 | 698 | ), |
692 | 699 | |
| 700 | + /* Testing */ |
| 701 | + |
| 702 | + 'mediawiki.tests.qunit.testrunner' => array( |
| 703 | + 'scripts' => 'tests/qunit/data/testrunner.js', |
| 704 | + 'dependencies' => array( |
| 705 | + 'jquery.qunit', |
| 706 | + 'jquery.qunit.completenessTest', |
| 707 | + ), |
| 708 | + 'position' => 'top', |
| 709 | + ), |
| 710 | + |
693 | 711 | /* MediaWiki Legacy */ |
694 | 712 | |
695 | 713 | 'mediawiki.legacy.ajax' => array( |
Index: branches/JSTesting/resources/mediawiki.special/mediawiki.special.javaScriptTest.js |
— | — | @@ -0,0 +1,31 @@ |
| 2 | +/* |
| 3 | + * JavaScript for Special:JavaScriptTest |
| 4 | + */ |
| 5 | +jQuery( document ).ready( function( $ ) { |
| 6 | + |
| 7 | + // Create useskin dropdown menu and reload onchange to the selected skin |
| 8 | + // (only if a framework was found, not on error pages). |
| 9 | + $( '#mw-javascripttest-summary.mw-javascripttest-frameworkfound' ).append( function() { |
| 10 | + |
| 11 | + var $html = $( '<p><label for="useskin">' |
| 12 | + + mw.message( 'javascripttest-pagetext-skins' ).escaped() + '</label></p>' ), |
| 13 | + select = '<select name="useskin" id="useskin">'; |
| 14 | + |
| 15 | + // Build <select> further |
| 16 | + $.each( mw.config.get( 'wgAvailableSkins' ), function( id ) { |
| 17 | + select += '<option value="' + id + '"' |
| 18 | + + ( mw.config.get( 'skin' ) === id ? ' selected="selected"' : '' ) |
| 19 | + + '>' + mw.message( 'skinname-' + id ).escaped() + '</option>'; |
| 20 | + } ); |
| 21 | + select += '</select>'; |
| 22 | + |
| 23 | + // Bind onchange event handler and append to form |
| 24 | + $html.append( |
| 25 | + $( select ).change( function() { |
| 26 | + window.location = QUnit.url( { useskin: $(this).val() } ); |
| 27 | + } ) |
| 28 | + ); |
| 29 | + |
| 30 | + return $html; |
| 31 | + } ); |
| 32 | +} ); |
Index: branches/JSTesting/tests/qunit/QUnitTestResources.php |
— | — | @@ -0,0 +1,49 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +return array( |
| 5 | + |
| 6 | + /* Resources of QUnit test suite for MediaWiki core */ |
| 7 | + |
| 8 | + 'mediawiki.tests.qunit.suites' => array( |
| 9 | + 'scripts' => array( |
| 10 | + 'tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js', |
| 11 | + 'tests/qunit/suites/resources/jquery/jquery.byteLength.test.js', |
| 12 | + 'tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js', // mw-config def |
| 13 | + 'tests/qunit/suites/resources/jquery/jquery.client.test.js', |
| 14 | + 'tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js', |
| 15 | + 'tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js', |
| 16 | + 'tests/qunit/suites/resources/jquery/jquery.localize.test.js', |
| 17 | + 'tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js', |
| 18 | + 'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js', |
| 19 | + # jquery.tablesorter.test.js: Broken |
| 20 | + #'tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js', // mw-config def |
| 21 | + 'tests/qunit/suites/resources/jquery/jquery.textSelection.test.js', |
| 22 | + # mediawiki.test.js: Broken due to relative path to /data/defineTestCallback.js |
| 23 | + #'tests/qunit/suites/resources/mediawiki/mediawiki.test.js', |
| 24 | + 'tests/qunit/suites/resources/mediawiki/mediawiki.title.test.js', // mw-config def |
| 25 | + 'tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js', |
| 26 | + 'tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js', |
| 27 | + 'tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js', |
| 28 | + |
| 29 | + // mw-config def: Contains mw.config defaults, fix when /qunit/index.html is removed |
| 30 | + ), |
| 31 | + 'dependencies' => array( |
| 32 | + 'jquery.autoEllipsis', |
| 33 | + 'jquery.byteLength', |
| 34 | + 'jquery.byteLimit', |
| 35 | + 'jquery.client', |
| 36 | + 'jquery.colorUtil', |
| 37 | + 'jquery.getAttrs', |
| 38 | + 'jquery.localize', |
| 39 | + 'jquery.mwExtension', |
| 40 | + 'jquery.tabIndex', |
| 41 | + 'jquery.tablesorter', |
| 42 | + 'jquery.textSelection', |
| 43 | + 'mediawiki', |
| 44 | + 'mediawiki.Title', |
| 45 | + 'mediawiki.user', |
| 46 | + 'mediawiki.util', |
| 47 | + 'mediawiki.special.recentchanges', |
| 48 | + ), |
| 49 | + ) |
| 50 | +); |
Index: branches/JSTesting/includes/resourceloader/ResourceLoader.php |
— | — | @@ -38,6 +38,10 @@ |
39 | 39 | /** Associative array mapping module name to info associative array */ |
40 | 40 | protected $moduleInfos = array(); |
41 | 41 | |
| 42 | + /** Associative array mapping framework ids to list of test suite module(s) */ |
| 43 | + /** like array( 'qunit' => array( 'mediawiki.tests.qunit.suites', 'ext.foo.tests', .. ), .. ) */ |
| 44 | + protected $testModuleNames = array(); |
| 45 | + |
42 | 46 | /** array( 'source-id' => array( 'loadScript' => 'http://.../load.php' ) ) **/ |
43 | 47 | protected $sources = array(); |
44 | 48 | |
— | — | @@ -183,7 +187,7 @@ |
184 | 188 | * Registers core modules and runs registration hooks. |
185 | 189 | */ |
186 | 190 | public function __construct() { |
187 | | - global $IP, $wgResourceModules, $wgResourceLoaderSources, $wgLoadScript; |
| 191 | + global $IP, $wgResourceModules, $wgResourceLoaderSources, $wgLoadScript, $wgEnableJavaScriptTest; |
188 | 192 | |
189 | 193 | wfProfileIn( __METHOD__ ); |
190 | 194 | |
— | — | @@ -199,6 +203,29 @@ |
200 | 204 | wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) ); |
201 | 205 | $this->register( $wgResourceModules ); |
202 | 206 | |
| 207 | + if ( $wgEnableJavaScriptTest === true ) { |
| 208 | + |
| 209 | + // Get core test suites |
| 210 | + $testModules = array(); |
| 211 | + $testModules['qunit'] = include( "$IP/tests/qunit/QUnitTestResources.php" ); |
| 212 | + // Allow extensions to add test suites |
| 213 | + wfRunHooks( 'ResourceLoaderTestModules', array( &$testModules, &$this ) ); |
| 214 | + |
| 215 | + foreach( $testModules as $id => $names ) { |
| 216 | + |
| 217 | + // Add the testrunner to the dependencies to make sure it's loaded first |
| 218 | + foreach( $names as $name ) { |
| 219 | + $testModules[$id][$name]['dependencies'][] = 'mediawiki.tests.qunit.testrunner'; |
| 220 | + } |
| 221 | + |
| 222 | + // Register test modules |
| 223 | + $this->register( $testModules[$id] ); |
| 224 | + |
| 225 | + // Keep track of their names so that they can be loaded together |
| 226 | + $this->testModuleNames[$id] = array_keys( $testModules[$id] ); |
| 227 | + } |
| 228 | + } |
| 229 | + |
203 | 230 | wfProfileOut( __METHOD__ ); |
204 | 231 | } |
205 | 232 | |
— | — | @@ -307,6 +334,21 @@ |
308 | 335 | } |
309 | 336 | |
310 | 337 | /** |
| 338 | + * Get a list of test module names for one (or all) frameworks. |
| 339 | + * |
| 340 | + * @return Array |
| 341 | + */ |
| 342 | + public function getTestModuleNames( $framework = 'all' ) { |
| 343 | + if ( $framework == 'all' ) { |
| 344 | + return $this->testModuleNames; |
| 345 | + } elseif ( isset( $this->testModuleNames[$framework] ) && is_array( $this->testModuleNames[$framework] ) ) { |
| 346 | + return $this->testModuleNames[$framework]; |
| 347 | + } else { |
| 348 | + return array(); |
| 349 | + } |
| 350 | + } |
| 351 | + |
| 352 | + /** |
311 | 353 | * Get the ResourceLoaderModule object for a given module name. |
312 | 354 | * |
313 | 355 | * @param $name String: Module name |
Index: branches/JSTesting/includes/AutoLoader.php |
— | — | @@ -777,6 +777,7 @@ |
778 | 778 | 'SpecialExport' => 'includes/specials/SpecialExport.php', |
779 | 779 | 'SpecialFilepath' => 'includes/specials/SpecialFilepath.php', |
780 | 780 | 'SpecialImport' => 'includes/specials/SpecialImport.php', |
| 781 | + 'SpecialJavaScriptTest' => 'includes/specials/SpecialJavaScriptTest.php', |
781 | 782 | 'SpecialListFiles' => 'includes/specials/SpecialListfiles.php', |
782 | 783 | 'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php', |
783 | 784 | 'SpecialListUsers' => 'includes/specials/SpecialListusers.php', |
Index: branches/JSTesting/includes/Skin.php |
— | — | @@ -22,7 +22,7 @@ |
23 | 23 | |
24 | 24 | /** |
25 | 25 | * Fetch the set of available skins. |
26 | | - * @return array of strings |
| 26 | + * @return associative array of strings |
27 | 27 | */ |
28 | 28 | static function getSkinNames() { |
29 | 29 | global $wgValidSkinNames; |
— | — | @@ -57,6 +57,18 @@ |
58 | 58 | } |
59 | 59 | |
60 | 60 | /** |
| 61 | + * Fetch the skinname messages for available skins. |
| 62 | + * @return array of strings |
| 63 | + */ |
| 64 | + static function getSkinNameMessages() { |
| 65 | + $messages = array(); |
| 66 | + foreach( self::getSkinNames() as $skinKey => $skinName ) { |
| 67 | + $messages[] = "skinname-$skinKey"; |
| 68 | + } |
| 69 | + return $messages; |
| 70 | + } |
| 71 | + |
| 72 | + /** |
61 | 73 | * Fetch the list of usable skins in regards to $wgSkipSkins. |
62 | 74 | * Useful for Special:Preferences and other places where you |
63 | 75 | * only want to show skins users _can_ use. |
Index: branches/JSTesting/includes/DefaultSettings.php |
— | — | @@ -4160,6 +4160,18 @@ |
4161 | 4161 | */ |
4162 | 4162 | $wgWikiID = false; |
4163 | 4163 | |
| 4164 | +/** |
| 4165 | + * Allow running of javascript test suites via [[Special:JavaScriptTest]] (such as QUnit). |
| 4166 | + */ |
| 4167 | +$wgEnableJavaScriptTest = false; |
| 4168 | + |
| 4169 | +/** |
| 4170 | + * Configuration for QUnit testing (ie. through [[Special:JavaScriptTest/qunit]]). |
| 4171 | + */ |
| 4172 | +$wgQUnitConfig = array( |
| 4173 | + 'documentation' => 'http://www.mediawiki.org/wiki/Manual:JavaScript_unit_testing', |
| 4174 | +); |
| 4175 | + |
4164 | 4176 | /** @} */ # end of profiling, testing and debugging } |
4165 | 4177 | |
4166 | 4178 | /************************************************************************//** |
— | — | @@ -5203,6 +5215,7 @@ |
5204 | 5216 | 'Specialpages' => 'other', |
5205 | 5217 | 'Blockme' => 'other', |
5206 | 5218 | 'Booksources' => 'other', |
| 5219 | + 'JavaScriptTest' => 'other', |
5207 | 5220 | ); |
5208 | 5221 | |
5209 | 5222 | /** Whether or not to sort special pages in Special:Specialpages */ |
Index: branches/JSTesting/includes/SpecialPageFactory.php |
— | — | @@ -138,6 +138,7 @@ |
139 | 139 | 'Blankpage' => 'SpecialBlankpage', |
140 | 140 | 'Blockme' => 'SpecialBlockme', |
141 | 141 | 'Emailuser' => 'SpecialEmailUser', |
| 142 | + 'JavaScriptTest' => 'SpecialJavaScriptTest', |
142 | 143 | 'Movepage' => 'MovePageForm', |
143 | 144 | 'Mycontributions' => 'SpecialMycontributions', |
144 | 145 | 'Mypage' => 'SpecialMypage', |
Index: branches/JSTesting/includes/specials/SpecialJavaScriptTest.php |
— | — | @@ -0,0 +1,122 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class SpecialJavaScriptTest extends SpecialPage { |
| 5 | + |
| 6 | + /** |
| 7 | + * @var $frameworks Array: Mapping of framework ids and their initilizer methods |
| 8 | + * in this class. If a framework is requested but not in this array, |
| 9 | + * the 'unknownframework' error is served. |
| 10 | + */ |
| 11 | + static $frameworks = array( |
| 12 | + 'qunit' => 'initQUnitTesting', |
| 13 | + ); |
| 14 | + |
| 15 | + public function __construct() { |
| 16 | + parent::__construct( 'JavaScriptTest' ); |
| 17 | + } |
| 18 | + |
| 19 | + public function execute( $par ) { |
| 20 | + global $wgEnableJavaScriptTest; |
| 21 | + |
| 22 | + $out = $this->getOutput(); |
| 23 | + |
| 24 | + $this->setHeaders(); |
| 25 | + $out->disallowUserJs(); |
| 26 | + |
| 27 | + // Abort early if we're disabled |
| 28 | + if ( $wgEnableJavaScriptTest !== true ) { |
| 29 | + $out->addWikiMsg( 'javascripttest-disabled' ); |
| 30 | + return; |
| 31 | + } |
| 32 | + |
| 33 | + $out->addModules( 'mediawiki.special.javaScriptTest' ); |
| 34 | + |
| 35 | + // Determine framework |
| 36 | + $pars = explode( '/', $par ); |
| 37 | + $framework = strtolower( $pars[0] ); |
| 38 | + |
| 39 | + // No framework specified |
| 40 | + if ( $par == '' ) { |
| 41 | + $out->setPagetitle( wfMsg( 'javascripttest' ) ); |
| 42 | + $summary = $this->wrapSummaryHtml( |
| 43 | + wfMsg( 'javascripttest-pagetext-noframework' ) . $this->getFrameworkListHtml(), |
| 44 | + 'noframework' |
| 45 | + ); |
| 46 | + $out->addHtml( $summary ); |
| 47 | + |
| 48 | + // Matched! Display proper title and initialize the framework |
| 49 | + } elseif ( isset( self::$frameworks[$framework] ) ) { |
| 50 | + $out->setPagetitle( wfMsg( 'javascripttest-title', wfMsg( "javascripttest-$framework-name" ) ) ); |
| 51 | + $this->{self::$frameworks[$framework]}(); |
| 52 | + |
| 53 | + // Framework not found, display error |
| 54 | + } else { |
| 55 | + $out->setPagetitle( wfMsg( 'javascripttest' ) ); |
| 56 | + $summary = $this->wrapSummaryHtml( '<p class="error">' |
| 57 | + . wfMsg( 'javascripttest-pagetext-unknownframework', $par ) |
| 58 | + . '</p>' |
| 59 | + . $this->getFrameworkListHtml() ); |
| 60 | + $out->addHtml( $summary, 'unknownframework' ); |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * Get a list of frameworks (including introduction paragraph and links to the framework run pages) |
| 66 | + * @return String: HTML |
| 67 | + */ |
| 68 | + private function getFrameworkListHtml() { |
| 69 | + $list = '<ul>'; |
| 70 | + foreach( self::$frameworks as $framework => $initFn ) { |
| 71 | + $list .= Html::rawElement( |
| 72 | + 'li', |
| 73 | + array(), |
| 74 | + Linker::link( $this->getTitle( $framework ), wfMsg( "javascripttest-$framework-name" ) ) |
| 75 | + ); |
| 76 | + } |
| 77 | + $list .= '</ul>'; |
| 78 | + $msg = wfMessage( 'javascripttest-pagetext-frameworks' )->rawParams( $list )->parseAsBlock(); |
| 79 | + |
| 80 | + return $msg; |
| 81 | + } |
| 82 | + |
| 83 | + /** |
| 84 | + * Function to wrap the summary. |
| 85 | + * @param $html String: The raw HTML. |
| 86 | + * @param $state String: State, one of 'noframework', 'unknownframework' or 'frameworkfound' |
| 87 | + */ |
| 88 | + private function wrapSummaryHtml( $html = '', $state ) { |
| 89 | + return "<div id=\"mw-javascripttest-summary\" class=\"mw-javascripttest-$state\">$html</div>"; |
| 90 | + } |
| 91 | + |
| 92 | + /** |
| 93 | + * Initialize the page for QUnit. |
| 94 | + */ |
| 95 | + private function initQUnitTesting() { |
| 96 | + global $wgQUnitConfig; |
| 97 | + |
| 98 | + $out = $this->getOutput(); |
| 99 | + |
| 100 | + $out->addModules( 'mediawiki.tests.qunit.testrunner' ); |
| 101 | + $qunitTestModules = $out->getResourceLoader()->getTestModuleNames( 'qunit' ); |
| 102 | + $out->addModules( $qunitTestModules ); |
| 103 | + |
| 104 | + $summary = wfMessage( 'javascripttest-qunit-intro' )->params( $wgQUnitConfig['documentation'] )->parseAsBlock(); |
| 105 | + $header = wfMessage( 'javascripttest-qunit-heading' )->escaped(); |
| 106 | + |
| 107 | + $baseHtml = <<<HTML |
| 108 | +<div id="qunit-header">$header</div> |
| 109 | +<div id="qunit-banner"></div> |
| 110 | +<div id="qunit-testrunner-toolbar"></div> |
| 111 | +<div id="qunit-userAgent"></div> |
| 112 | +<ol id="qunit-tests"></ol> |
| 113 | +HTML; |
| 114 | + $out->addHtml( $this->wrapSummaryHtml( $summary, 'frameworkfound' ) . $baseHtml ); |
| 115 | + |
| 116 | + } |
| 117 | + |
| 118 | + public function isListed(){ |
| 119 | + global $wgEnableJavaScriptTest; |
| 120 | + return $wgEnableJavaScriptTest === true; |
| 121 | + } |
| 122 | + |
| 123 | +} |