3 use MediaWiki\MediaWikiServices
;
4 use Wikimedia\Rdbms\IDatabase
;
5 use Wikimedia\TestingAccessWrapper
;
8 * @covers ResourceLoaderWikiModule
10 class ResourceLoaderWikiModuleTest
extends ResourceLoaderTestCase
{
13 * @dataProvider provideConstructor
15 public function testConstructor( $params ) {
16 $module = new ResourceLoaderWikiModule( $params );
17 $this->assertInstanceOf( ResourceLoaderWikiModule
::class, $module );
20 public static function provideConstructor() {
21 yield
'null' => [ null ];
22 yield
'empty' => [ [] ];
23 yield
'unknown settings' => [ [ 'foo' => 'baz' ] ];
24 yield
'real settings' => [ [ 'MediaWiki:Common.js' ] ];
27 private function prepareTitleInfo( array $mockInfo ) {
28 $module = TestingAccessWrapper
::newFromClass( ResourceLoaderWikiModule
::class );
30 foreach ( $mockInfo as $key => $val ) {
31 $info[ $module->makeTitleKey( Title
::newFromText( $key ) ) ] = $val;
37 * @dataProvider provideGetPages
39 public function testGetPages( $params, Config
$config, $expected ) {
40 $module = new ResourceLoaderWikiModule( $params );
41 $module->setConfig( $config );
43 // Because getPages is protected..
44 $getPages = new ReflectionMethod( $module, 'getPages' );
45 $getPages->setAccessible( true );
46 $out = $getPages->invoke( $module, ResourceLoaderContext
::newDummyContext() );
47 $this->assertSame( $expected, $out );
50 public static function provideGetPages() {
51 $settings = self
::getSettings() +
[
57 'styles' => [ 'MediaWiki:Common.css' ],
58 'scripts' => [ 'MediaWiki:Common.js' ],
62 [ [], new HashConfig( $settings ), [] ],
63 [ $params, new HashConfig( $settings ), [
64 'MediaWiki:Common.js' => [ 'type' => 'script' ],
65 'MediaWiki:Common.css' => [ 'type' => 'style' ]
67 [ $params, new HashConfig( [ 'UseSiteCss' => false ] +
$settings ), [
68 'MediaWiki:Common.js' => [ 'type' => 'script' ],
70 [ $params, new HashConfig( [ 'UseSiteJs' => false ] +
$settings ), [
71 'MediaWiki:Common.css' => [ 'type' => 'style' ],
75 [ 'UseSiteJs' => false, 'UseSiteCss' => false ]
83 * @dataProvider provideGetGroup
85 public function testGetGroup( $params, $expected ) {
86 $module = new ResourceLoaderWikiModule( $params );
87 $this->assertSame( $expected, $module->getGroup() );
90 public static function provideGetGroup() {
91 yield
'no group' => [ [], null ];
92 yield
'some group' => [ [ 'group' => 'foobar' ], 'foobar' ];
96 * @dataProvider provideIsKnownEmpty
98 public function testIsKnownEmpty( $titleInfo, $group, $dependencies, $expected ) {
99 $module = $this->getMockBuilder( ResourceLoaderWikiModule
::class )
100 ->disableOriginalConstructor()
101 ->setMethods( [ 'getTitleInfo', 'getGroup', 'getDependencies' ] )
103 $module->method( 'getTitleInfo' )
104 ->willReturn( $this->prepareTitleInfo( $titleInfo ) );
105 $module->method( 'getGroup' )
106 ->willReturn( $group );
107 $module->method( 'getDependencies' )
108 ->willReturn( $dependencies );
109 $context = $this->createMock( ResourceLoaderContext
::class );
110 $this->assertSame( $expected, $module->isKnownEmpty( $context ) );
113 public static function provideIsKnownEmpty() {
118 // No pages exist, considered empty.
122 yield
'an empty page exists (no group)' => [
123 [ 'Project:Example/foo.js' => [ 'page_len' => 0 ] ],
126 // There is an existing page, so we should let the module be queued.
127 // Its emptiness might be temporary, hence considered non-empty (T70488).
130 yield
'an empty page exists (site group)' => [
131 [ 'MediaWiki:Foo.js' => [ 'page_len' => 0 ] ],
134 // There is an existing page, hence considered non-empty.
137 yield
'an empty page exists (user group)' => [
138 [ 'User:Example/foo.js' => [ 'page_len' => 0 ] ],
141 // There is an existing page, but it is empty.
142 // For user-specific modules, don't bother loading a known-empty module.
143 // Given user-specific HTML output, this will vary and re-appear if/when
144 // the page becomes non-empty again.
148 yield
'no pages but having dependencies (no group)' => [
151 [ 'another-module' ],
154 yield
'no pages but having dependencies (site group)' => [
157 [ 'another-module' ],
160 yield
'no pages but having dependencies (user group)' => [
163 [ 'another-module' ],
167 yield
'a non-empty page exists (user group)' => [
168 [ 'User:Example/foo.js' => [ 'page_len' => 25 ] ],
173 yield
'a non-empty page exists (site group)' => [
174 [ 'MediaWiki:Foo.js' => [ 'page_len' => 25 ] ],
181 public function testGetTitleInfo() {
183 'MediaWiki:Common.css' => [ 'type' => 'styles' ],
184 'mediawiki: fallback.css' => [ 'type' => 'styles' ],
186 $titleInfo = $this->prepareTitleInfo( [
187 'MediaWiki:Common.css' => [ 'page_len' => 1234 ],
188 'MediaWiki:Fallback.css' => [ 'page_len' => 0 ],
190 $expected = $titleInfo;
192 $module = $this->getMockBuilder( ResourceLoaderWikiModule
::class )
193 ->setMethods( [ 'getPages', 'getTitleInfo' ] )
195 $module->method( 'getPages' )->willReturn( $pages );
196 $module->method( 'getTitleInfo' )->willReturn( $titleInfo );
198 $context = $this->getMockBuilder( ResourceLoaderContext
::class )
199 ->disableOriginalConstructor()
202 $module = TestingAccessWrapper
::newFromObject( $module );
203 $this->assertSame( $expected, $module->getTitleInfo( $context ), 'Title info' );
206 public function testGetPreloadedTitleInfo() {
208 'MediaWiki:Common.css' => [ 'type' => 'styles' ],
209 // Regression against T145673. It's impossible to statically declare page names in
210 // a canonical way since the canonical prefix is localised. As such, the preload
211 // cache computed the right cache key, but failed to find the results when
212 // doing an intersect on the canonical result, producing an empty array.
213 'mediawiki: fallback.css' => [ 'type' => 'styles' ],
215 $titleInfo = $this->prepareTitleInfo( [
216 'MediaWiki:Common.css' => [ 'page_len' => 1234 ],
217 'MediaWiki:Fallback.css' => [ 'page_len' => 0 ],
219 $expected = $titleInfo;
221 $module = $this->getMockBuilder( TestResourceLoaderWikiModule
::class )
222 ->setMethods( [ 'getPages' ] )
224 $module->method( 'getPages' )->willReturn( $pages );
225 // Can't mock static methods
226 $module::$returnFetchTitleInfo = $titleInfo;
228 $rl = new EmptyResourceLoader();
229 $context = new ResourceLoaderContext( $rl, new FauxRequest() );
231 TestResourceLoaderWikiModule
::invalidateModuleCache(
232 Title
::newFromText( 'MediaWiki:Common.css' ),
237 TestResourceLoaderWikiModule
::preloadTitleInfo(
239 $this->createMock( IDatabase
::class ),
243 $module = TestingAccessWrapper
::newFromObject( $module );
244 $this->assertSame( $expected, $module->getTitleInfo( $context ), 'Title info' );
247 public function testGetPreloadedBadTitle() {
249 TestResourceLoaderWikiModule
::$returnFetchTitleInfo = [];
250 $rl = new EmptyResourceLoader();
251 $rl->getConfig()->set( 'UseSiteJs', true );
252 $rl->getConfig()->set( 'UseSiteCss', true );
253 $rl->register( 'testmodule', [
254 'class' => TestResourceLoaderWikiModule
::class,
255 // Covers preloadTitleInfo branch for invalid page name
256 'styles' => [ '[x]' ],
258 $context = new ResourceLoaderContext( $rl, new FauxRequest() );
261 TestResourceLoaderWikiModule
::preloadTitleInfo(
263 $this->createMock( IDatabase
::class ),
268 $module = TestingAccessWrapper
::newFromObject( $rl->getModule( 'testmodule' ) );
269 $this->assertSame( [], $module->getTitleInfo( $context ), 'Title info' );
272 public function testGetPreloadedTitleInfoEmpty() {
273 $context = new ResourceLoaderContext( new EmptyResourceLoader(), new FauxRequest() );
274 // This covers the early return case
277 ResourceLoaderWikiModule
::preloadTitleInfo(
279 $this->createMock( IDatabase
::class ),
285 public static function provideGetContent() {
286 yield
'Bad title' => [ null, '[x]' ];
287 yield
'Dead redirect' => [ null, [
288 'text' => 'Dead redirect',
289 'title' => 'Dead_redirect',
292 yield
'Bad content model' => [ null, [
293 'text' => 'MediaWiki:Wikitext',
294 'ns' => NS_MEDIAWIKI
,
295 'title' => 'Wikitext',
297 yield
'No JS content found' => [ null, [
298 'text' => 'MediaWiki:Script.js',
299 'ns' => NS_MEDIAWIKI
,
300 'title' => 'Script.js',
302 yield
'No CSS content found' => [ null, [
303 'text' => 'MediaWiki:Styles.css',
304 'ns' => NS_MEDIAWIKI
,
305 'title' => 'Script.css',
310 * @dataProvider provideGetContent
312 public function testGetContent( $expected, $title ) {
313 $context = $this->getResourceLoaderContext( [], new EmptyResourceLoader
);
314 $module = $this->getMockBuilder( ResourceLoaderWikiModule
::class )
315 ->setMethods( [ 'getContentObj' ] )->getMock();
316 $module->method( 'getContentObj' )
317 ->willReturn( null );
319 if ( is_array( $title ) ) {
320 $title +
= [ 'ns' => NS_MAIN
, 'id' => 1, 'len' => 1, 'redirect' => 0 ];
321 $titleText = $title['text'];
322 // Mock Title db access via LinkCache
323 MediaWikiServices
::getInstance()->getLinkCache()->addGoodLinkObj(
325 new TitleValue( $title['ns'], $title['title'] ),
333 $module = TestingAccessWrapper
::newFromObject( $module );
336 $module->getContent( $titleText, $context )
340 public function testContentOverrides() {
342 'MediaWiki:Common.css' => [ 'type' => 'style' ],
345 $module = $this->getMockBuilder( ResourceLoaderWikiModule
::class )
346 ->setMethods( [ 'getPages' ] )
348 $module->method( 'getPages' )->willReturn( $pages );
350 $rl = new EmptyResourceLoader();
351 $context = new DerivativeResourceLoaderContext(
352 new ResourceLoaderContext( $rl, new FauxRequest() )
354 $context->setContentOverrideCallback( function ( Title
$t ) {
355 if ( $t->getPrefixedText() === 'MediaWiki:Common.css' ) {
356 return new CssContent( '.override{}' );
361 $this->assertTrue( $module->shouldEmbedModule( $context ) );
364 "/*\nMediaWiki:Common.css\n*/\n.override{}"
366 ], $module->getStyles( $context ) );
368 $context->setContentOverrideCallback( function ( Title
$t ) {
369 if ( $t->getPrefixedText() === 'MediaWiki:Skin.css' ) {
370 return new CssContent( '.override{}' );
374 $this->assertFalse( $module->shouldEmbedModule( $context ) );
377 public function testGetContentForRedirects() {
378 // Set up context and module object
379 $context = new DerivativeResourceLoaderContext(
380 $this->getResourceLoaderContext( [], new EmptyResourceLoader
)
382 $module = $this->getMockBuilder( ResourceLoaderWikiModule
::class )
383 ->setMethods( [ 'getPages' ] )
385 $module->method( 'getPages' )
387 'MediaWiki:Redirect.js' => [ 'type' => 'script' ]
389 $context->setContentOverrideCallback( function ( Title
$title ) {
390 if ( $title->getPrefixedText() === 'MediaWiki:Redirect.js' ) {
391 $handler = new JavaScriptContentHandler();
392 return $handler->makeRedirectContent(
393 Title
::makeTitle( NS_MEDIAWIKI
, 'Target.js' )
395 } elseif ( $title->getPrefixedText() === 'MediaWiki:Target.js' ) {
396 return new JavaScriptContent( 'target;' );
402 // Mock away Title's db queries with LinkCache
403 MediaWikiServices
::getInstance()->getLinkCache()->addGoodLinkObj(
405 new TitleValue( NS_MEDIAWIKI
, 'Redirect.js' ),
411 "/*\nMediaWiki:Redirect.js\n*/\ntarget;\n",
412 $module->getScript( $context ),
413 'Redirect resolved by getContent'
417 public function tearDown() {
418 Title
::clearCaches();
423 class TestResourceLoaderWikiModule
extends ResourceLoaderWikiModule
{
424 public static $returnFetchTitleInfo = null;
426 protected static function fetchTitleInfo( IDatabase
$db, array $pages, $fname = null ) {
427 $ret = self
::$returnFetchTitleInfo;
428 self
::$returnFetchTitleInfo = null;