resourceloader: Clean up ResourceLoaderWikiModuleTest
[lhc/web/wiklou.git] / tests / phpunit / includes / resourceloader / ResourceLoaderWikiModuleTest.php
1 <?php
2
3 use MediaWiki\MediaWikiServices;
4 use Wikimedia\Rdbms\IDatabase;
5 use Wikimedia\TestingAccessWrapper;
6
7 /**
8 * @covers ResourceLoaderWikiModule
9 */
10 class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
11
12 /**
13 * @dataProvider provideConstructor
14 */
15 public function testConstructor( $params ) {
16 $module = new ResourceLoaderWikiModule( $params );
17 $this->assertInstanceOf( ResourceLoaderWikiModule::class, $module );
18 }
19
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' ] ];
25 }
26
27 private function prepareTitleInfo( array $mockInfo ) {
28 $module = TestingAccessWrapper::newFromClass( ResourceLoaderWikiModule::class );
29 $info = [];
30 foreach ( $mockInfo as $key => $val ) {
31 $info[ $module->makeTitleKey( Title::newFromText( $key ) ) ] = $val;
32 }
33 return $info;
34 }
35
36 /**
37 * @dataProvider provideGetPages
38 */
39 public function testGetPages( $params, Config $config, $expected ) {
40 $module = new ResourceLoaderWikiModule( $params );
41 $module->setConfig( $config );
42
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 );
48 }
49
50 public static function provideGetPages() {
51 $settings = self::getSettings() + [
52 'UseSiteJs' => true,
53 'UseSiteCss' => true,
54 ];
55
56 $params = [
57 'styles' => [ 'MediaWiki:Common.css' ],
58 'scripts' => [ 'MediaWiki:Common.js' ],
59 ];
60
61 return [
62 [ [], new HashConfig( $settings ), [] ],
63 [ $params, new HashConfig( $settings ), [
64 'MediaWiki:Common.js' => [ 'type' => 'script' ],
65 'MediaWiki:Common.css' => [ 'type' => 'style' ]
66 ] ],
67 [ $params, new HashConfig( [ 'UseSiteCss' => false ] + $settings ), [
68 'MediaWiki:Common.js' => [ 'type' => 'script' ],
69 ] ],
70 [ $params, new HashConfig( [ 'UseSiteJs' => false ] + $settings ), [
71 'MediaWiki:Common.css' => [ 'type' => 'style' ],
72 ] ],
73 [ $params,
74 new HashConfig(
75 [ 'UseSiteJs' => false, 'UseSiteCss' => false ]
76 ),
77 []
78 ],
79 ];
80 }
81
82 /**
83 * @dataProvider provideGetGroup
84 */
85 public function testGetGroup( $params, $expected ) {
86 $module = new ResourceLoaderWikiModule( $params );
87 $this->assertSame( $expected, $module->getGroup() );
88 }
89
90 public static function provideGetGroup() {
91 yield 'no group' => [ [], null ];
92 yield 'some group' => [ [ 'group' => 'foobar' ], 'foobar' ];
93 }
94
95 /**
96 * @dataProvider provideIsKnownEmpty
97 */
98 public function testIsKnownEmpty( $titleInfo, $group, $dependencies, $expected ) {
99 $module = $this->getMockBuilder( ResourceLoaderWikiModule::class )
100 ->disableOriginalConstructor()
101 ->setMethods( [ 'getTitleInfo', 'getGroup', 'getDependencies' ] )
102 ->getMock();
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 ) );
111 }
112
113 public static function provideIsKnownEmpty() {
114 yield 'nothing' => [
115 [],
116 null,
117 [],
118 // No pages exist, considered empty.
119 true,
120 ];
121
122 yield 'an empty page exists (no group)' => [
123 [ 'Project:Example/foo.js' => [ 'page_len' => 0 ] ],
124 null,
125 [],
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).
128 false,
129 ];
130 yield 'an empty page exists (site group)' => [
131 [ 'MediaWiki:Foo.js' => [ 'page_len' => 0 ] ],
132 'site',
133 [],
134 // There is an existing page, hence considered non-empty.
135 false,
136 ];
137 yield 'an empty page exists (user group)' => [
138 [ 'User:Example/foo.js' => [ 'page_len' => 0 ] ],
139 'user',
140 [],
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.
145 true,
146 ];
147
148 yield 'no pages but having dependencies (no group)' => [
149 [],
150 null,
151 [ 'another-module' ],
152 false,
153 ];
154 yield 'no pages but having dependencies (site group)' => [
155 [],
156 'site',
157 [ 'another-module' ],
158 false,
159 ];
160 yield 'no pages but having dependencies (user group)' => [
161 [],
162 'user',
163 [ 'another-module' ],
164 false,
165 ];
166
167 yield 'a non-empty page exists (user group)' => [
168 [ 'User:Example/foo.js' => [ 'page_len' => 25 ] ],
169 'user',
170 [],
171 false,
172 ];
173 yield 'a non-empty page exists (site group)' => [
174 [ 'MediaWiki:Foo.js' => [ 'page_len' => 25 ] ],
175 'site',
176 [],
177 false,
178 ];
179 }
180
181 public function testGetTitleInfo() {
182 $pages = [
183 'MediaWiki:Common.css' => [ 'type' => 'styles' ],
184 'mediawiki: fallback.css' => [ 'type' => 'styles' ],
185 ];
186 $titleInfo = $this->prepareTitleInfo( [
187 'MediaWiki:Common.css' => [ 'page_len' => 1234 ],
188 'MediaWiki:Fallback.css' => [ 'page_len' => 0 ],
189 ] );
190 $expected = $titleInfo;
191
192 $module = $this->getMockBuilder( ResourceLoaderWikiModule::class )
193 ->setMethods( [ 'getPages', 'getTitleInfo' ] )
194 ->getMock();
195 $module->method( 'getPages' )->willReturn( $pages );
196 $module->method( 'getTitleInfo' )->willReturn( $titleInfo );
197
198 $context = $this->getMockBuilder( ResourceLoaderContext::class )
199 ->disableOriginalConstructor()
200 ->getMock();
201
202 $module = TestingAccessWrapper::newFromObject( $module );
203 $this->assertSame( $expected, $module->getTitleInfo( $context ), 'Title info' );
204 }
205
206 public function testGetPreloadedTitleInfo() {
207 $pages = [
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' ],
214 ];
215 $titleInfo = $this->prepareTitleInfo( [
216 'MediaWiki:Common.css' => [ 'page_len' => 1234 ],
217 'MediaWiki:Fallback.css' => [ 'page_len' => 0 ],
218 ] );
219 $expected = $titleInfo;
220
221 $module = $this->getMockBuilder( TestResourceLoaderWikiModule::class )
222 ->setMethods( [ 'getPages' ] )
223 ->getMock();
224 $module->method( 'getPages' )->willReturn( $pages );
225 // Can't mock static methods
226 $module::$returnFetchTitleInfo = $titleInfo;
227
228 $rl = new EmptyResourceLoader();
229 $context = new ResourceLoaderContext( $rl, new FauxRequest() );
230
231 TestResourceLoaderWikiModule::invalidateModuleCache(
232 Title::newFromText( 'MediaWiki:Common.css' ),
233 null,
234 null,
235 wfWikiID()
236 );
237 TestResourceLoaderWikiModule::preloadTitleInfo(
238 $context,
239 $this->createMock( IDatabase::class ),
240 [ 'testmodule' ]
241 );
242
243 $module = TestingAccessWrapper::newFromObject( $module );
244 $this->assertSame( $expected, $module->getTitleInfo( $context ), 'Title info' );
245 }
246
247 public function testGetPreloadedBadTitle() {
248 // Set up
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]' ],
257 ] );
258 $context = new ResourceLoaderContext( $rl, new FauxRequest() );
259
260 // Act
261 TestResourceLoaderWikiModule::preloadTitleInfo(
262 $context,
263 $this->createMock( IDatabase::class ),
264 [ 'testmodule' ]
265 );
266
267 // Assert
268 $module = TestingAccessWrapper::newFromObject( $rl->getModule( 'testmodule' ) );
269 $this->assertSame( [], $module->getTitleInfo( $context ), 'Title info' );
270 }
271
272 public function testGetPreloadedTitleInfoEmpty() {
273 $context = new ResourceLoaderContext( new EmptyResourceLoader(), new FauxRequest() );
274 // This covers the early return case
275 $this->assertSame(
276 null,
277 ResourceLoaderWikiModule::preloadTitleInfo(
278 $context,
279 $this->createMock( IDatabase::class ),
280 []
281 )
282 );
283 }
284
285 public static function provideGetContent() {
286 yield 'Bad title' => [ null, '[x]' ];
287 yield 'Dead redirect' => [ null, [
288 'text' => 'Dead redirect',
289 'title' => 'Dead_redirect',
290 'redirect' => 1,
291 ] ];
292 yield 'Bad content model' => [ null, [
293 'text' => 'MediaWiki:Wikitext',
294 'ns' => NS_MEDIAWIKI,
295 'title' => 'Wikitext',
296 ] ];
297 yield 'No JS content found' => [ null, [
298 'text' => 'MediaWiki:Script.js',
299 'ns' => NS_MEDIAWIKI,
300 'title' => 'Script.js',
301 ] ];
302 yield 'No CSS content found' => [ null, [
303 'text' => 'MediaWiki:Styles.css',
304 'ns' => NS_MEDIAWIKI,
305 'title' => 'Script.css',
306 ] ];
307 }
308
309 /**
310 * @dataProvider provideGetContent
311 */
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 );
318
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(
324 $title['id'],
325 new TitleValue( $title['ns'], $title['title'] ),
326 $title['len'],
327 $title['redirect']
328 );
329 } else {
330 $titleText = $title;
331 }
332
333 $module = TestingAccessWrapper::newFromObject( $module );
334 $this->assertSame(
335 $expected,
336 $module->getContent( $titleText, $context )
337 );
338 }
339
340 public function testContentOverrides() {
341 $pages = [
342 'MediaWiki:Common.css' => [ 'type' => 'style' ],
343 ];
344
345 $module = $this->getMockBuilder( ResourceLoaderWikiModule::class )
346 ->setMethods( [ 'getPages' ] )
347 ->getMock();
348 $module->method( 'getPages' )->willReturn( $pages );
349
350 $rl = new EmptyResourceLoader();
351 $context = new DerivativeResourceLoaderContext(
352 new ResourceLoaderContext( $rl, new FauxRequest() )
353 );
354 $context->setContentOverrideCallback( function ( Title $t ) {
355 if ( $t->getPrefixedText() === 'MediaWiki:Common.css' ) {
356 return new CssContent( '.override{}' );
357 }
358 return null;
359 } );
360
361 $this->assertTrue( $module->shouldEmbedModule( $context ) );
362 $this->assertSame( [
363 'all' => [
364 "/*\nMediaWiki:Common.css\n*/\n.override{}"
365 ]
366 ], $module->getStyles( $context ) );
367
368 $context->setContentOverrideCallback( function ( Title $t ) {
369 if ( $t->getPrefixedText() === 'MediaWiki:Skin.css' ) {
370 return new CssContent( '.override{}' );
371 }
372 return null;
373 } );
374 $this->assertFalse( $module->shouldEmbedModule( $context ) );
375 }
376
377 public function testGetContentForRedirects() {
378 // Set up context and module object
379 $context = new DerivativeResourceLoaderContext(
380 $this->getResourceLoaderContext( [], new EmptyResourceLoader )
381 );
382 $module = $this->getMockBuilder( ResourceLoaderWikiModule::class )
383 ->setMethods( [ 'getPages' ] )
384 ->getMock();
385 $module->method( 'getPages' )
386 ->willReturn( [
387 'MediaWiki:Redirect.js' => [ 'type' => 'script' ]
388 ] );
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' )
394 );
395 } elseif ( $title->getPrefixedText() === 'MediaWiki:Target.js' ) {
396 return new JavaScriptContent( 'target;' );
397 } else {
398 return null;
399 }
400 } );
401
402 // Mock away Title's db queries with LinkCache
403 MediaWikiServices::getInstance()->getLinkCache()->addGoodLinkObj(
404 1, // id
405 new TitleValue( NS_MEDIAWIKI, 'Redirect.js' ),
406 1, // len
407 1 // redirect
408 );
409
410 $this->assertSame(
411 "/*\nMediaWiki:Redirect.js\n*/\ntarget;\n",
412 $module->getScript( $context ),
413 'Redirect resolved by getContent'
414 );
415 }
416
417 public function tearDown() {
418 Title::clearCaches();
419 parent::tearDown();
420 }
421 }
422
423 class TestResourceLoaderWikiModule extends ResourceLoaderWikiModule {
424 public static $returnFetchTitleInfo = null;
425
426 protected static function fetchTitleInfo( IDatabase $db, array $pages, $fname = null ) {
427 $ret = self::$returnFetchTitleInfo;
428 self::$returnFetchTitleInfo = null;
429 return $ret;
430 }
431 }