/** @var int[] */
private static $pageCount = [];
+ static function unload() {
+ self::$loaded = false;
+ }
+
static function recache() {
self::load( true );
}
: self::LIMIT_SML1;
if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
+ $this->logFeatureUsage( "too-many-$valueName-for-{$this->getModulePath()}" );
$this->setWarning( "Too many values supplied for parameter '$valueName': " .
"the limit is $sizeLimit" );
}
implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n"
);
- $shutdownPositions = $this->shutdownPositions;
- $ok = $this->store->merge(
- $this->key,
- function ( $store, $key, $curValue ) use ( $shutdownPositions ) {
- /** @var $curPositions DBMasterPos[] */
- if ( $curValue === false ) {
- $curPositions = $shutdownPositions;
- } else {
- $curPositions = $curValue['positions'];
- // Use the newest positions for each DB master
- foreach ( $shutdownPositions as $db => $pos ) {
- if ( !isset( $curPositions[$db] )
- || $pos->asOfTime() > $curPositions[$db]->asOfTime()
- ) {
- $curPositions[$db] = $pos;
- }
- }
- }
-
- return [ 'positions' => $curPositions ];
- },
- BagOStuff::TTL_MINUTE,
- 10,
- BagOStuff::WRITE_SYNC // visible in all datacenters
- );
+ // CP-protected writes should overwhemingly go to the master datacenter, so get DC-local
+ // lock to merge the values. Use a DC-local get() and a synchronous all-DC set(). This
+ // makes it possible for the BagOStuff class to write in parallel to all DCs with one RTT.
+ if ( $this->store->lock( $this->key, 3 ) ) {
+ $ok = $this->store->set(
+ $this->key,
+ self::mergePositions( $this->store->get( $this->key ), $this->shutdownPositions ),
+ BagOStuff::TTL_MINUTE,
+ BagOStuff::WRITE_SYNC
+ );
+ $this->store->unlock( $this->key );
+ } else {
+ $ok = false;
+ }
if ( !$ok ) {
// Raced out too many times or stash is down
wfDebugLog( 'replication', __METHOD__ . ": key is {$this->key} (unread)\n" );
}
}
+
+ /**
+ * @param array|bool $curValue
+ * @param DBMasterPos[] $shutdownPositions
+ * @return array
+ */
+ private static function mergePositions( $curValue, array $shutdownPositions ) {
+ /** @var $curPositions DBMasterPos[] */
+ if ( $curValue === false ) {
+ $curPositions = $shutdownPositions;
+ } else {
+ $curPositions = $curValue['positions'];
+ // Use the newest positions for each DB master
+ foreach ( $shutdownPositions as $db => $pos ) {
+ if ( !isset( $curPositions[$db] )
+ || $pos->asOfTime() > $curPositions[$db]->asOfTime()
+ ) {
+ $curPositions[$db] = $pos;
+ }
+ }
+ }
+
+ return [ 'positions' => $curPositions ];
+ }
}
// Commit the updates and unlock the table
$dbw->unlock( $lockKey, __METHOD__ );
}
+
+ // Invalid cache used by parser functions
+ SiteStats::unload();
}
/**
__METHOD__
);
+ // Invalid cache used by parser functions
+ SiteStats::unload();
+
return $activeUsers;
}
}
$fieldType = get_class( $this );
- $helpText = $this->getHelpText();
+ $help = $this->getHelpText();
$errors = $this->getErrorsRaw( $value );
foreach ( $errors as &$error ) {
$error = new OOUI\HtmlSnippet( $error );
$config = [
'classes' => [ "mw-htmlform-field-$fieldType", $this->mClass ],
'align' => $this->getLabelAlignOOUI(),
- 'help' => $helpText !== null ? new OOUI\HtmlSnippet( $helpText ) : null,
+ 'help' => ( $help !== null && $help !== '' ) ? new OOUI\HtmlSnippet( $help ) : null,
'errors' => $errors,
'notices' => $notices,
'infusable' => $infusable,
// 1.28
[ 'addIndex', 'recentchanges', 'rc_name_type_patrolled_timestamp',
'patch-add-rc_name_type_patrolled_timestamp_index.sql' ],
+ [ 'doRevisionPageRevIndexNonUnique' ]
];
}
'Making user_id unsigned int'
);
}
+
+ protected function doRevisionPageRevIndexNonUnique() {
+ if ( !$this->doTable( 'revision' ) ) {
+ return true;
+ } elseif ( !$this->db->indexExists( 'revision', 'rev_page_id' ) ) {
+ $this->output( "...rev_page_id index not found on revision.\n" );
+ return true;
+ }
+
+ if ( !$this->db->indexUnique( 'revision', 'rev_page_id' ) ) {
+ $this->output( "...rev_page_id index already non-unique.\n" );
+ return true;
+ }
+
+ return $this->applyPatch(
+ 'patch-revision-page-rev-index-nonunique.sql',
+ false,
+ 'Making rev_page_id index non-unique'
+ );
+ }
}
* - HTTPProxy : HTTP proxy to use (optional)
* - parsoidCompat : whether to parse URL as if they were meant for Parsoid
* boolean (optional)
+ * - fixedUrl : Do not append domain to the url. For example to use
+ * English Wikipedia restbase, you would this to true
+ * and url to https://en.wikipedia.org/api/rest_#version#
*/
public function __construct( array $params ) {
// set up defaults and merge them with the given params
'timeout' => 100,
'forwardCookies' => false,
'HTTPProxy' => null,
- 'parsoidCompat' => false
+ 'parsoidCompat' => false,
+ 'fixedUrl' => false,
], $params );
// Ensure that the url parameter has a trailing slash.
$mparams['url'] = preg_replace(
$result = [];
foreach ( $reqs as $key => $req ) {
- // replace /local/ with the current domain
- $req['url'] = preg_replace( '#^local/#', $this->params['domain'] . '/', $req['url'] );
- // and prefix it with the service URL
- $req['url'] = $this->params['url'] . $req['url'];
+ if ( $this->params['fixedUrl'] ) {
+ $version = explode( '/', $req['url'] )[1];
+ $req['url'] =
+ str_replace( '#version#', $version, $this->params['url'] ) .
+ preg_replace( '#^local/v./#', '', $req['url'] );
+ } else {
+ // replace /local/ with the current domain
+ $req['url'] = preg_replace( '#^local/#', $this->params['domain'] . '/', $req['url'] );
+ // and prefix it with the service URL
+ $req['url'] = $this->params['url'] . $req['url'];
+ }
+
// set the appropriate proxy, timeout and headers
if ( $this->params['HTTPProxy'] ) {
$req['proxy'] = $this->params['HTTPProxy'];
}
return $result;
-
}
/**
/** @var int */
protected $syncTimeout = 3;
+ /** @var LoadBalancer|null */
+ protected $separateMainLB;
/** @var array */
protected $conns;
/** @var array UNIX timestamps */
$this->serverInfos = [ $params['server'] ];
$this->numServers = count( $this->serverInfos );
} else {
+ // Default to using the main wiki's database servers
$this->serverInfos = false;
$this->numServers = 1;
}
$this->slaveOnly = !empty( $params['slaveOnly'] );
}
+ protected function getSeparateMainLB() {
+ global $wgDBtype;
+
+ if ( $wgDBtype === 'mysql' && $this->usesMainDB() ) {
+ if ( !$this->separateMainLB ) {
+ // We must keep a separate connection to MySQL in order to avoid deadlocks
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $this->separateMainLB = $lbFactory->newMainLB();
+ }
+ return $this->separateMainLB;
+ } else {
+ // However, SQLite has an opposite behavior. And PostgreSQL needs to know
+ // if we are in transaction or not (@TODO: find some PostgreSQL work-around).
+ return null;
+ }
+ }
+
/**
* Get a connection to the specified database
*
$db = DatabaseBase::factory( $type, $info );
$db->clearFlag( DBO_TRX );
} else {
- // We must keep a separate connection to MySQL in order to avoid deadlocks
- // However, SQLite has an opposite behavior. And PostgreSQL needs to know
- // if we are in transaction or not (@TODO: find some work-around).
$index = $this->slaveOnly ? DB_SLAVE : DB_MASTER;
- if ( wfGetDB( $index )->getType() == 'mysql' ) {
- $lb = wfGetLBFactory()->newMainLB();
- $db = $lb->getConnection( $index );
+ if ( $this->getSeparateMainLB() ) {
+ $db = $this->getSeparateMainLB()->getConnection( $index );
$db->clearFlag( DBO_TRX ); // auto-commit mode
} else {
$db = wfGetDB( $index );
+ // Can't mess with transaction rounds (DBO_TRX) :(
}
}
$this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $db ) );
protected function waitForSlaves() {
if ( $this->usesMainDB() ) {
+ $lb = $this->getSeparateMainLB()
+ ?: MediaWikiServices::getInstance()->getDBLoadBalancer();
// Main LB is used; wait for any slaves to catch up
try {
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- $lbFactory->waitForReplication( [ 'wiki' => wfWikiID() ] );
- return true;
+ $pos = $lb->getMasterPos();
+ if ( $pos ) {
+ return $lb->waitForAll( $pos, 3 );
+ }
} catch ( DBReplicationWaitError $e ) {
return false;
}
- } else {
- // Custom DB server list; probably doesn't use replication
- return true;
}
+
+ // Custom DB server list; probably doesn't use replication
+ return true;
}
}
protected $moduleInfos = [];
/** @var Config $config */
- private $config;
+ protected $config;
/**
* Associative array mapping framework ids to a list of names of test suite modules
* Register sources with the given IDs and properties.
*
* @param string $id Source ID
- * @param array $properties Source properties (see addSource())
+ * @param string $loadUrl load.php url
* @return string
*/
- public static function makeLoaderSourcesScript( $id, $properties = null ) {
+ public static function makeLoaderSourcesScript( $id, $loadUrl = null ) {
if ( is_array( $id ) ) {
return Xml::encodeJsCall(
'mw.loader.addSource',
} else {
return Xml::encodeJsCall(
'mw.loader.addSource',
- [ $id, $properties ],
+ [ $id, $loadUrl ],
ResourceLoader::inDebugMode()
);
}
}
/**
- * Returns true if the form built from the given AuthenticationRequests has fields which take
- * values. If all available providers use the redirect flow, the form might contain nothing
- * but submit buttons, in which case we should not add an extra submit button which does nothing.
+ * Returns true if the form built from the given AuthenticationRequests needs a submit button.
+ * Providers using redirect flow (e.g. Google login) need their own submit buttons; if using
+ * one of those custom buttons is the only way to proceed, there is no point in displaying the
+ * default button which won't do anything useful.
*
* @param AuthenticationRequest[] $requests An array of AuthenticationRequests from which the
* form will be built
* @return bool
*/
protected function needsSubmitButton( array $requests ) {
+ $customSubmitButtonPresent = false;
+
+ // Secondary and preauth providers always need their data; they will not care what button
+ // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
+ // that's the point in being optional. Se we need to check whether all primary providers
+ // have their own buttons and whether there is at least one button present.
foreach ( $requests as $req ) {
- if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED &&
- $this->doesRequestNeedsSubmitButton( $req )
- ) {
- return true;
+ if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
+ if ( $this->hasOwnSubmitButton( $req ) ) {
+ $customSubmitButtonPresent = true;
+ } else {
+ return true;
+ }
}
}
- return false;
+ return !$customSubmitButtonPresent;
}
/**
- * Checks if the given AuthenticationRequest needs a submit button or not.
- *
- * @param AuthenticationRequest $req The request to check
+ * Checks whether the given AuthenticationRequest has its own submit button.
+ * @param AuthenticationRequest $req
* @return bool
*/
- protected function doesRequestNeedsSubmitButton( AuthenticationRequest $req ) {
+ protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
foreach ( $req->getFieldInfo() as $field => $info ) {
if ( $info['type'] === 'button' ) {
- return false;
+ return true;
}
}
- return true;
+ return false;
}
/**
--- /dev/null
+-- Makes rev_page_id index non-unique
+ALTER TABLE /*_*/revision
+DROP INDEX /*i*/rev_page_id;
+
+CREATE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
-- In case tables are created as MyISAM, use row hints for MySQL <5.0 to avoid 4GB limit
-CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
.movepage-wrapper {
width: 50em;
- margin: 1em 0;
}
/* OOUIHTMLForm styles */
-.mw-htmlform-ooui-wrapper {
- margin: 1em 0;
-}
-
.mw-htmlform-ooui .mw-htmlform-submit-buttons {
margin-top: 1em;
}
'ResourceLoaderTestCase' => "$testDir/phpunit/ResourceLoaderTestCase.php",
'ResourceLoaderTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
'ResourceLoaderFileModuleTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
+ 'EmptyResourceLoader' => "$testDir/phpunit/ResourceLoaderTestCase.php",
'TestUser' => "$testDir/phpunit/includes/TestUser.php",
'TestUserRegistry' => "$testDir/phpunit/includes/TestUserRegistry.php",
'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
<?php
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
abstract class ResourceLoaderTestCase extends MediaWikiTestCase {
/**
* @param string $lang
class ResourceLoaderFileModuleTestModule extends ResourceLoaderFileModule {
}
+
+class EmptyResourceLoader extends ResourceLoader {
+ // TODO: This won't be needed once ResourceLoader is empty by default
+ // and default registrations are done from ServiceWiring instead.
+ public function __construct( Config $config = null, LoggerInterface $logger = null ) {
+ $this->setLogger( $logger ?: new NullLogger() );
+ $this->config = $config ?: ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ $this->setMessageBlobStore( new MessageBlobStore( $this, $this->getLogger() ) );
+ }
+}
*/
protected $mOptions;
/**
- * @var Preprocessor
+ * @var array
*/
- protected $mPreprocessor;
+ protected $mPreprocessors;
+
+ protected static $classNames = [
+ 'Preprocessor_DOM',
+ 'Preprocessor_Hash'
+ ];
protected function setUp() {
- global $wgParserConf, $wgContLang;
+ global $wgContLang;
parent::setUp();
$this->mOptions = ParserOptions::newFromUserAndLang( new User, $wgContLang );
- $name = isset( $wgParserConf['preprocessorClass'] )
- ? $wgParserConf['preprocessorClass']
- : 'Preprocessor_DOM';
- $this->mPreprocessor = new $name( $this );
+ $this->mPreprocessors = [];
+ foreach ( self::$classNames as $className ) {
+ $this->mPreprocessors[$className] = new $className( $this );
+ }
}
function getStripList() {
return [ 'gallery', 'display map' /* Used by Maps, see r80025 CR */, '/foo' ];
}
+ protected static function addClassArg( $testCases ) {
+ $newTestCases = [];
+ foreach ( self::$classNames as $className ) {
+ foreach ( $testCases as $testCase ) {
+ array_unshift( $testCase, $className );
+ $newTestCases[] = $testCase;
+ }
+ }
+ return $newTestCases;
+ }
+
public static function provideCases() {
// @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
- return [
+ return self::addClassArg( [
[ "Foo", "<root>Foo</root>" ],
[ "<!-- Foo -->", "<root><comment><!-- Foo --></comment></root>" ],
[ "<!-- Foo --><!-- Bar -->", "<root><comment><!-- Foo --></comment><comment><!-- Bar --></comment></root>" ],
[ "{{Foo|} Bar=", "<root>{{Foo|} Bar=</root>" ],
[ "{{Foo|} Bar=}}", "<root><template><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>" ],
/* [ file_get_contents( __DIR__ . '/QuoteQuran.txt' ], file_get_contents( __DIR__ . '/QuoteQuranExpanded.txt' ) ], */
- ];
+ ] );
// @codingStandardsIgnoreEnd
}
* Get XML preprocessor tree from the preprocessor (which may not be the
* native XML-based one).
*
+ * @param string $className
* @param string $wikiText
* @return string
*/
- protected function preprocessToXml( $wikiText ) {
- if ( method_exists( $this->mPreprocessor, 'preprocessToXml' ) ) {
- return $this->normalizeXml( $this->mPreprocessor->preprocessToXml( $wikiText ) );
+ protected function preprocessToXml( $className, $wikiText ) {
+ $preprocessor = $this->mPreprocessors[$className];
+ if ( method_exists( $preprocessor, 'preprocessToXml' ) ) {
+ return $this->normalizeXml( $preprocessor->preprocessToXml( $wikiText ) );
}
- $dom = $this->mPreprocessor->preprocessToObj( $wikiText );
+ $dom = $preprocessor->preprocessToObj( $wikiText );
if ( is_callable( [ $dom, 'saveXML' ] ) ) {
return $dom->saveXML();
} else {
* @return string
*/
protected function normalizeXml( $xml ) {
- return preg_replace( '!<([a-z]+)/>!', '<$1></$1>', str_replace( ' />', '/>', $xml ) );
+ // Normalize self-closing tags
+ $xml = preg_replace( '!<([a-z]+)/>!', '<$1></$1>', str_replace( ' />', '/>', $xml ) );
+ // Remove <equals> tags, which only occur in Preprocessor_Hash and
+ // have no semantic value
+ $xml = preg_replace( '!</?equals>!', '', $xml );
+ return $xml;
}
/**
* @dataProvider provideCases
* @covers Preprocessor_DOM::preprocessToXml
*/
- public function testPreprocessorOutput( $wikiText, $expectedXml ) {
- $this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) );
+ public function testPreprocessorOutput( $className, $wikiText, $expectedXml ) {
+ $this->assertEquals( $this->normalizeXml( $expectedXml ),
+ $this->preprocessToXml( $className, $wikiText ) );
}
/**
*/
public static function provideFiles() {
// @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
- return [
+ return self::addClassArg( [
[ "QuoteQuran" ], # http://en.wikipedia.org/w/index.php?title=Template:QuoteQuran/sandbox&oldid=237348988 GFDL + CC BY-SA by Striver
[ "Factorial" ], # http://en.wikipedia.org/w/index.php?title=Template:Factorial&oldid=98548758 GFDL + CC BY-SA by Polonium
[ "All_system_messages" ], # http://tl.wiktionary.org/w/index.php?title=Suleras:All_system_messages&oldid=2765 GPL text generated by MediaWiki
[ "Fundraising" ], # http://tl.wiktionary.org/w/index.php?title=MediaWiki:Sitenotice&oldid=5716 GFDL + CC BY-SA, copied there by Sky Harbor.
[ "NestedTemplates" ], # bug 27936
- ];
+ ] );
// @codingStandardsIgnoreEnd
}
* @dataProvider provideFiles
* @covers Preprocessor_DOM::preprocessToXml
*/
- public function testPreprocessorOutputFiles( $filename ) {
+ public function testPreprocessorOutputFiles( $className, $filename ) {
$folder = __DIR__ . "/../../../parser/preprocess";
$wikiText = file_get_contents( "$folder/$filename.txt" );
- $output = $this->preprocessToXml( $wikiText );
+ $output = $this->preprocessToXml( $className, $wikiText );
$expectedFilename = "$folder/$filename.expected";
if ( file_exists( $expectedFilename ) ) {
*/
public static function provideHeadings() {
// @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
- return [ /* These should become headings: */
+ return self::addClassArg( [
+ /* These should become headings: */
[ "== h ==<!--c1-->", "<root><h level=\"2\" i=\"1\">== h ==<comment><!--c1--></comment></h></root>" ],
[ "== h == <!--c1-->", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment></h></root>" ],
[ "== h ==<!--c1--> ", "<root><h level=\"2\" i=\"1\">== h ==<comment><!--c1--></comment> </h></root>" ],
[ "== h == x <!--c1--><!--c2--><!--c3--> ", "<root>== h == x <comment><!--c1--></comment><comment><!--c2--></comment><comment><!--c3--></comment> </root>" ],
[ "== h ==<!--c1--> x <!--c2--><!--c3--> ", "<root>== h ==<comment><!--c1--></comment> x <comment><!--c2--></comment><comment><!--c3--></comment> </root>" ],
[ "== h ==<!--c1--><!--c2--><!--c3--> x ", "<root>== h ==<comment><!--c1--></comment><comment><!--c2--></comment><comment><!--c3--></comment> x </root>" ],
- ];
+ ] );
// @codingStandardsIgnoreEnd
}
* @dataProvider provideHeadings
* @covers Preprocessor_DOM::preprocessToXml
*/
- public function testHeadings( $wikiText, $expectedXml ) {
- $this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) );
+ public function testHeadings( $className, $wikiText, $expectedXml ) {
+ $this->assertEquals( $this->normalizeXml( $expectedXml ),
+ $this->preprocessToXml( $className, $wikiText ) );
}
}
* @dataProvider provideGetModuleRegistrations
* @covers ResourceLoaderStartUpModule::compileUnresolvedDependencies
* @covers ResourceLoaderStartUpModule::getModuleRegistrations
- * @covers ResourceLoader::makeLoaderSourcesScript
* @covers ResourceLoader::makeLoaderRegisterScript
*/
public function testGetModuleRegistrations( $case ) {
] );
}
- public static function provideValidModules() {
- return [
- [ 'TEST.validModule1', new ResourceLoaderTestModule() ],
- ];
- }
-
/**
- * Ensures that the ResourceLoaderRegisterModules hook is called when a new
- * ResourceLoader object is constructed.
+ * Ensure the ResourceLoaderRegisterModules hook is called.
+ *
* @covers ResourceLoader::__construct
*/
- public function testCreatingNewResourceLoaderCallsRegistrationHook() {
+ public function testConstructRegistrationHook() {
$resourceLoaderRegisterModulesHook = false;
$this->setMwGlobals( 'wgHooks', [
]
] );
- $resourceLoader = new ResourceLoader();
+ $unused = new ResourceLoader();
$this->assertTrue(
$resourceLoaderRegisterModulesHook,
'Hook ResourceLoaderRegisterModules called'
);
-
- return $resourceLoader;
}
/**
- * @dataProvider provideValidModules
- * @depends testCreatingNewResourceLoaderCallsRegistrationHook
* @covers ResourceLoader::register
* @covers ResourceLoader::getModule
*/
- public function testRegisteredValidModulesAreAccessible(
- $name, ResourceLoaderModule $module, ResourceLoader $resourceLoader
- ) {
- $resourceLoader->register( $name, $module );
- $this->assertEquals( $module, $resourceLoader->getModule( $name ) );
+ public function testRegisterValid() {
+ $module = new ResourceLoaderTestModule();
+ $resourceLoader = new EmptyResourceLoader();
+ $resourceLoader->register( 'test', $module );
+ $this->assertEquals( $module, $resourceLoader->getModule( 'test' ) );
}
/**
- * @covers ResourceLoaderFileModule::compileLessFile
+ * @covers ResourceLoader::register
*/
- public function testLessFileCompilation() {
- $context = $this->getResourceLoaderContext();
- $basePath = __DIR__ . '/../../data/less/module';
- $module = new ResourceLoaderFileModule( [
- 'localBasePath' => $basePath,
- 'styles' => [ 'styles.less' ],
- ] );
- $module->setName( 'test.less' );
- $styles = $module->getStyles( $context );
- $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
+ public function testRegisterInvalidName() {
+ $resourceLoader = new EmptyResourceLoader();
+ $this->setExpectedException( 'MWException', "name 'test!invalid' is invalid" );
+ $resourceLoader->register( 'test!invalid', new ResourceLoaderTestModule() );
}
/**
- * Strip @noflip annotations from CSS code.
- * @param string $css
- * @return string
+ * @covers ResourceLoader::register
*/
- private static function stripNoflip( $css ) {
- return str_replace( '/*@noflip*/ ', '', $css );
+ public function testRegisterInvalidType() {
+ $resourceLoader = new EmptyResourceLoader();
+ $this->setExpectedException( 'MWException', 'ResourceLoader module info type error' );
+ $resourceLoader->register( 'test', new stdClass() );
}
/**
- * @dataProvider providePackedModules
- * @covers ResourceLoader::makePackedModulesString
+ * @covers ResourceLoader::getModuleNames
*/
- public function testMakePackedModulesString( $desc, $modules, $packed ) {
- $this->assertEquals( $packed, ResourceLoader::makePackedModulesString( $modules ), $desc );
+ public function testGetModuleNames() {
+ // Use an empty one so that core and extension modules don't get in.
+ $resourceLoader = new EmptyResourceLoader();
+ $resourceLoader->register( 'test.foo', new ResourceLoaderTestModule() );
+ $resourceLoader->register( 'test.bar', new ResourceLoaderTestModule() );
+ $this->assertEquals(
+ [ 'test.foo', 'test.bar' ],
+ $resourceLoader->getModuleNames()
+ );
}
/**
- * @dataProvider providePackedModules
- * @covers ResourceLoaderContext::expandModuleNames
+ * @covers ResourceLoader::isModuleRegistered
*/
- public function testexpandModuleNames( $desc, $modules, $packed ) {
- $this->assertEquals( $modules, ResourceLoaderContext::expandModuleNames( $packed ), $desc );
+ public function testIsModuleRegistered() {
+ $rl = new EmptyResourceLoader();
+ $rl->register( 'test', new ResourceLoaderTestModule() );
+ $this->assertTrue( $rl->isModuleRegistered( 'test' ) );
+ $this->assertFalse( $rl->isModuleRegistered( 'test.unknown' ) );
+ }
+
+ /**
+ * @covers ResourceLoader::getModule
+ */
+ public function testGetModuleUnknown() {
+ $rl = new EmptyResourceLoader();
+ $this->assertSame( null, $rl->getModule( 'test' ) );
+ }
+
+ /**
+ * @covers ResourceLoader::getModule
+ */
+ public function testGetModuleClass() {
+ $rl = new EmptyResourceLoader();
+ $rl->register( 'test', [ 'class' => ResourceLoaderTestModule::class ] );
+ $this->assertInstanceOf(
+ ResourceLoaderTestModule::class,
+ $rl->getModule( 'test' )
+ );
+ }
+
+ /**
+ * @covers ResourceLoader::getModule
+ */
+ public function testGetModuleClassDefault() {
+ $rl = new EmptyResourceLoader();
+ $rl->register( 'test', [] );
+ $this->assertInstanceOf(
+ ResourceLoaderFileModule::class,
+ $rl->getModule( 'test' ),
+ 'Array-style module registrations default to FileModule'
+ );
+ }
+
+ /**
+ * @covers ResourceLoaderFileModule::compileLessFile
+ */
+ public function testLessFileCompilation() {
+ $context = $this->getResourceLoaderContext();
+ $basePath = __DIR__ . '/../../data/less/module';
+ $module = new ResourceLoaderFileModule( [
+ 'localBasePath' => $basePath,
+ 'styles' => [ 'styles.less' ],
+ ] );
+ $module->setName( 'test.less' );
+ $styles = $module->getStyles( $context );
+ $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
}
public static function providePackedModules() {
];
}
+ /**
+ * @dataProvider providePackedModules
+ * @covers ResourceLoader::makePackedModulesString
+ */
+ public function testMakePackedModulesString( $desc, $modules, $packed ) {
+ $this->assertEquals( $packed, ResourceLoader::makePackedModulesString( $modules ), $desc );
+ }
+
+ /**
+ * @dataProvider providePackedModules
+ * @covers ResourceLoaderContext::expandModuleNames
+ */
+ public function testexpandModuleNames( $desc, $modules, $packed ) {
+ $this->assertEquals( $modules, ResourceLoaderContext::expandModuleNames( $packed ), $desc );
+ }
+
public static function provideAddSource() {
return [
- [ 'examplewiki', '//example.org/w/load.php', 'examplewiki' ],
- [ 'example2wiki', [ 'loadScript' => '//example.com/w/load.php' ], 'example2wiki' ],
+ [ 'foowiki', 'https://example.org/w/load.php', 'foowiki' ],
+ [ 'foowiki', [ 'loadScript' => 'https://example.org/w/load.php' ], 'foowiki' ],
[
- [ 'foowiki' => '//foo.org/w/load.php', 'bazwiki' => '//baz.org/w/load.php' ],
+ [
+ 'foowiki' => 'https://example.org/w/load.php',
+ 'bazwiki' => 'https://example.com/w/load.php',
+ ],
null,
[ 'foowiki', 'bazwiki' ]
- ],
- [
- [ 'foowiki' => '//foo.org/w/load.php' ],
- null,
- false,
- ],
+ ]
];
}
*/
public function testAddSource( $name, $info, $expected ) {
$rl = new ResourceLoader;
- if ( $expected === false ) {
- $this->setExpectedException( 'MWException', 'ResourceLoader duplicate source addition error' );
- $rl->addSource( $name, $info );
- }
$rl->addSource( $name, $info );
if ( is_array( $expected ) ) {
foreach ( $expected as $source ) {
}
}
- public static function fakeSources() {
- return [
- 'examplewiki' => [
- 'loadScript' => '//example.org/w/load.php',
- 'apiScript' => '//example.org/w/api.php',
- ],
- 'example2wiki' => [
- 'loadScript' => '//example.com/w/load.php',
- 'apiScript' => '//example.com/w/api.php',
- ],
- ];
+ /**
+ * @covers ResourceLoader::addSource
+ */
+ public function testAddSourceDupe() {
+ $rl = new ResourceLoader;
+ $this->setExpectedException( 'MWException', 'ResourceLoader duplicate source addition error' );
+ $rl->addSource( 'foo', 'https://example.org/w/load.php' );
+ $rl->addSource( 'foo', 'https://example.com/w/load.php' );
+ }
+
+ /**
+ * @covers ResourceLoader::addSource
+ */
+ public function testAddSourceInvalid() {
+ $rl = new ResourceLoader;
+ $this->setExpectedException( 'MWException', 'with no "loadScript" key' );
+ $rl->addSource( 'foo', [ 'x' => 'https://example.org/w/load.php' ] );
}
public static function provideLoaderImplement() {
'name' => 'test.example',
'scripts' => 'mw.example();',
'styles' => [],
- 'messages' => new XmlJsCode( '{}' ),
- 'templates' => [],
'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
mw.example();
'scripts' => [],
'styles' => [ 'css' => [ '.mw-example {}' ] ],
'messages' => new XmlJsCode( '{}' ),
- 'templates' => [],
'expected' => 'mw.loader.implement( "test.example", [], {
"css": [
'name' => 'test.example',
'scripts' => 'mw.example();',
- 'styles' => [],
'messages' => [ 'example' => '' ],
- 'templates' => [],
'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
mw.example();
'name' => 'test.example',
'scripts' => 'mw.example();',
- 'styles' => [],
- 'messages' => new XmlJsCode( '{}' ),
'templates' => [ 'example.html' => '' ],
'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
"example.html": ""
} );',
] ],
+ [ [
+ 'title' => 'Implement unwrapped user script',
+
+ 'name' => 'user',
+ 'scripts' => 'mw.example( 1 );',
+
+ 'expected' => 'mw.loader.implement( "user", "mw.example( 1 );" );',
+ ] ],
+ [ [
+ 'title' => 'Implement unwrapped user script',
+ 'debug' => false,
+
+ 'name' => 'user',
+ 'scripts' => 'mw.example( 1 );',
+
+ 'expected' => 'mw.loader.implement("user","mw.example(1);");',
+ ] ],
];
}
/**
* @dataProvider provideLoaderImplement
* @covers ResourceLoader::makeLoaderImplementScript
+ * @covers ResourceLoader::trimArray
*/
public function testMakeLoaderImplementScript( $case ) {
+ $case += [
+ 'styles' => [], 'templates' => [], 'messages' => new XmlJsCode( '{}' ),
+ 'debug' => true
+ ];
+ ResourceLoader::clearCache();
+ $this->setMwGlobals( 'wgResourceLoaderDebug', $case['debug'] );
+
$this->assertEquals(
$case['expected'],
ResourceLoader::makeLoaderImplementScript(
);
}
+ /**
+ * @covers ResourceLoader::makeLoaderImplementScript
+ */
+ public function testMakeLoaderImplementScriptInvalid() {
+ $this->setExpectedException( 'MWException', 'Invalid scripts error' );
+ ResourceLoader::makeLoaderImplementScript(
+ 'test', // name
+ 123, // scripts
+ null, // styles
+ null, // messages
+ null // templates
+ );
+ }
+
+ /**
+ * @covers ResourceLoader::makeLoaderSourcesScript
+ */
+ public function testMakeLoaderSourcesScript() {
+ $this->assertEquals(
+ 'mw.loader.addSource( "local", "/w/load.php" );',
+ ResourceLoader::makeLoaderSourcesScript( 'local', '/w/load.php' )
+ );
+ $this->assertEquals(
+ 'mw.loader.addSource( {
+ "local": "/w/load.php"
+} );',
+ ResourceLoader::makeLoaderSourcesScript( [ 'local' => '/w/load.php' ] )
+ );
+ $this->assertEquals(
+ 'mw.loader.addSource( {
+ "local": "/w/load.php",
+ "example": "https://example.org/w/load.php"
+} );',
+ ResourceLoader::makeLoaderSourcesScript( [
+ 'local' => '/w/load.php',
+ 'example' => 'https://example.org/w/load.php'
+ ] )
+ );
+ $this->assertEquals(
+ 'mw.loader.addSource( [] );',
+ ResourceLoader::makeLoaderSourcesScript( [] )
+ );
+ }
+
+ private static function fakeSources() {
+ return [
+ 'examplewiki' => [
+ 'loadScript' => '//example.org/w/load.php',
+ 'apiScript' => '//example.org/w/api.php',
+ ],
+ 'example2wiki' => [
+ 'loadScript' => '//example.com/w/load.php',
+ 'apiScript' => '//example.com/w/api.php',
+ ],
+ ];
+ }
+
/**
* @covers ResourceLoader::getLoadScript
*/
$this->assertTrue( true );
}
}
-
- /**
- * @covers ResourceLoader::isModuleRegistered
- */
- public function testIsModuleRegistered() {
- $rl = new ResourceLoader();
- $rl->register( 'test.module', new ResourceLoaderTestModule() );
- $this->assertTrue( $rl->isModuleRegistered( 'test.module' ) );
- $this->assertFalse( $rl->isModuleRegistered( 'test.modulenotregistered' ) );
- }
}