Merge "rdbms: implement strictor ownership of LoadBalancer by LBFactory"
[lhc/web/wiklou.git] / tests / phpunit / includes / resourceloader / ResourceLoaderFileModuleTest.php
1 <?php
2
3 /**
4 * @group Database
5 * @group ResourceLoader
6 */
7 class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase {
8
9 protected function setUp() {
10 parent::setUp();
11
12 $skinFactory = new SkinFactory();
13 // The return value of the closure shouldn't matter since this test should
14 // never call it
15 $skinFactory->register(
16 'fakeskin',
17 'FakeSkin',
18 function () {
19 }
20 );
21 $this->setService( 'SkinFactory', $skinFactory );
22 }
23
24 private static function getModules() {
25 $base = [
26 'localBasePath' => realpath( __DIR__ ),
27 ];
28
29 return [
30 'noTemplateModule' => [],
31
32 'deprecatedModule' => $base + [
33 'deprecated' => true,
34 ],
35 'deprecatedTomorrow' => $base + [
36 'deprecated' => 'Will be removed tomorrow.'
37 ],
38
39 'htmlTemplateModule' => $base + [
40 'templates' => [
41 'templates/template.html',
42 'templates/template2.html',
43 ]
44 ],
45
46 'htmlTemplateUnknown' => $base + [
47 'templates' => [
48 'templates/notfound.html',
49 ]
50 ],
51
52 'aliasedHtmlTemplateModule' => $base + [
53 'templates' => [
54 'foo.html' => 'templates/template.html',
55 'bar.html' => 'templates/template2.html',
56 ]
57 ],
58
59 'templateModuleHandlebars' => $base + [
60 'templates' => [
61 'templates/template_awesome.handlebars',
62 ],
63 ],
64
65 'aliasFooFromBar' => $base + [
66 'templates' => [
67 'foo.foo' => 'templates/template.bar',
68 ],
69 ],
70 ];
71 }
72
73 public static function providerTemplateDependencies() {
74 $modules = self::getModules();
75
76 return [
77 [
78 $modules['noTemplateModule'],
79 [],
80 ],
81 [
82 $modules['htmlTemplateModule'],
83 [
84 'mediawiki.template',
85 ],
86 ],
87 [
88 $modules['templateModuleHandlebars'],
89 [
90 'mediawiki.template',
91 'mediawiki.template.handlebars',
92 ],
93 ],
94 [
95 $modules['aliasFooFromBar'],
96 [
97 'mediawiki.template',
98 'mediawiki.template.foo',
99 ],
100 ],
101 ];
102 }
103
104 /**
105 * @dataProvider providerTemplateDependencies
106 * @covers ResourceLoaderFileModule::__construct
107 * @covers ResourceLoaderFileModule::getDependencies
108 */
109 public function testTemplateDependencies( $module, $expected ) {
110 $rl = new ResourceLoaderFileModule( $module );
111 $rl->setName( 'testing' );
112 $this->assertEquals( $rl->getDependencies(), $expected );
113 }
114
115 public static function providerDeprecatedModules() {
116 return [
117 [
118 'deprecatedModule',
119 'mw.log.warn("This page is using the deprecated ResourceLoader module \"deprecatedModule\".");',
120 ],
121 [
122 'deprecatedTomorrow',
123 'mw.log.warn(' .
124 '"This page is using the deprecated ResourceLoader module \"deprecatedTomorrow\".\\n' .
125 "Will be removed tomorrow." .
126 '");'
127 ]
128 ];
129 }
130
131 /**
132 * @dataProvider providerDeprecatedModules
133 * @covers ResourceLoaderFileModule::getScript
134 */
135 public function testDeprecatedModules( $name, $expected ) {
136 $modules = self::getModules();
137 $module = new ResourceLoaderFileModule( $modules[$name] );
138 $module->setName( $name );
139 $ctx = $this->getResourceLoaderContext();
140 $this->assertEquals( $module->getScript( $ctx ), $expected );
141 }
142
143 /**
144 * @covers ResourceLoaderFileModule::getScript
145 * @covers ResourceLoaderFileModule::getScriptFiles
146 * @covers ResourceLoaderFileModule::readScriptFiles
147 */
148 public function testGetScript() {
149 $module = new ResourceLoaderFileModule( [
150 'localBasePath' => __DIR__ . '/../../data/resourceloader',
151 'scripts' => [ 'script-nosemi.js', 'script-comment.js' ],
152 ] );
153 $module->setName( 'testing' );
154 $ctx = $this->getResourceLoaderContext();
155 $this->assertEquals(
156 "/* eslint-disable */\nmw.foo()\n" .
157 "\n" .
158 "/* eslint-disable */\nmw.foo()\n// mw.bar();\n" .
159 "\n",
160 $module->getScript( $ctx ),
161 'scripts are concatenated with a new-line'
162 );
163 }
164
165 /**
166 * @covers ResourceLoaderFileModule::getAllStyleFiles
167 * @covers ResourceLoaderFileModule::getAllSkinStyleFiles
168 * @covers ResourceLoaderFileModule::getSkinStyleFiles
169 */
170 public function testGetAllSkinStyleFiles() {
171 $baseParams = [
172 'scripts' => [
173 'foo.js',
174 'bar.js',
175 ],
176 'styles' => [
177 'foo.css',
178 'bar.css' => [ 'media' => 'print' ],
179 'screen.less' => [ 'media' => 'screen' ],
180 'screen-query.css' => [ 'media' => 'screen and (min-width: 400px)' ],
181 ],
182 'skinStyles' => [
183 'default' => 'quux-fallback.less',
184 'fakeskin' => [
185 'baz-vector.css',
186 'quux-vector.less',
187 ],
188 ],
189 'messages' => [
190 'hello',
191 'world',
192 ],
193 ];
194
195 $module = new ResourceLoaderFileModule( $baseParams );
196 $module->setName( 'testing' );
197
198 $this->assertEquals(
199 [
200 'foo.css',
201 'baz-vector.css',
202 'quux-vector.less',
203 'quux-fallback.less',
204 'bar.css',
205 'screen.less',
206 'screen-query.css',
207 ],
208 array_map( 'basename', $module->getAllStyleFiles() )
209 );
210 }
211
212 /**
213 * Strip @noflip annotations from CSS code.
214 * @param string $css
215 * @return string
216 */
217 private static function stripNoflip( $css ) {
218 return str_replace( '/*@noflip*/ ', '', $css );
219 }
220
221 /**
222 * What happens when you mix @embed and @noflip?
223 * This really is an integration test, but oh well.
224 *
225 * @covers ResourceLoaderFileModule::getStyles
226 * @covers ResourceLoaderFileModule::getStyleFiles
227 * @covers ResourceLoaderFileModule::readStyleFiles
228 * @covers ResourceLoaderFileModule::readStyleFile
229 */
230 public function testMixedCssAnnotations() {
231 $basePath = __DIR__ . '/../../data/css';
232 $testModule = new ResourceLoaderFileModule( [
233 'localBasePath' => $basePath,
234 'styles' => [ 'test.css' ],
235 ] );
236 $testModule->setName( 'testing' );
237 $expectedModule = new ResourceLoaderFileModule( [
238 'localBasePath' => $basePath,
239 'styles' => [ 'expected.css' ],
240 ] );
241 $expectedModule->setName( 'testing' );
242
243 $contextLtr = $this->getResourceLoaderContext( [
244 'lang' => 'en',
245 'dir' => 'ltr',
246 ] );
247 $contextRtl = $this->getResourceLoaderContext( [
248 'lang' => 'he',
249 'dir' => 'rtl',
250 ] );
251
252 // Since we want to compare the effect of @noflip+@embed against the effect of just @embed, and
253 // the @noflip annotations are always preserved, we need to strip them first.
254 $this->assertEquals(
255 $expectedModule->getStyles( $contextLtr ),
256 self::stripNoflip( $testModule->getStyles( $contextLtr ) ),
257 "/*@noflip*/ with /*@embed*/ gives correct results in LTR mode"
258 );
259 $this->assertEquals(
260 $expectedModule->getStyles( $contextLtr ),
261 self::stripNoflip( $testModule->getStyles( $contextRtl ) ),
262 "/*@noflip*/ with /*@embed*/ gives correct results in RTL mode"
263 );
264 }
265
266 public static function providerGetTemplates() {
267 $modules = self::getModules();
268
269 return [
270 [
271 $modules['noTemplateModule'],
272 [],
273 ],
274 [
275 $modules['templateModuleHandlebars'],
276 [
277 'templates/template_awesome.handlebars' => "wow\n",
278 ],
279 ],
280 [
281 $modules['htmlTemplateModule'],
282 [
283 'templates/template.html' => "<strong>hello</strong>\n",
284 'templates/template2.html' => "<div>goodbye</div>\n",
285 ],
286 ],
287 [
288 $modules['aliasedHtmlTemplateModule'],
289 [
290 'foo.html' => "<strong>hello</strong>\n",
291 'bar.html' => "<div>goodbye</div>\n",
292 ],
293 ],
294 [
295 $modules['htmlTemplateUnknown'],
296 false,
297 ],
298 ];
299 }
300
301 /**
302 * @dataProvider providerGetTemplates
303 * @covers ResourceLoaderFileModule::getTemplates
304 */
305 public function testGetTemplates( $module, $expected ) {
306 $rl = new ResourceLoaderFileModule( $module );
307 $rl->setName( 'testing' );
308
309 if ( $expected === false ) {
310 $this->setExpectedException( MWException::class );
311 $rl->getTemplates();
312 } else {
313 $this->assertEquals( $rl->getTemplates(), $expected );
314 }
315 }
316
317 /**
318 * @covers ResourceLoaderFileModule::stripBom
319 */
320 public function testBomConcatenation() {
321 $basePath = __DIR__ . '/../../data/css';
322 $testModule = new ResourceLoaderFileModule( [
323 'localBasePath' => $basePath,
324 'styles' => [ 'bom.css' ],
325 ] );
326 $testModule->setName( 'testing' );
327 $this->assertEquals(
328 substr( file_get_contents( "$basePath/bom.css" ), 0, 10 ),
329 "\xef\xbb\xbf.efbbbf",
330 'File has leading BOM'
331 );
332
333 $context = $this->getResourceLoaderContext();
334 $this->assertEquals(
335 $testModule->getStyles( $context ),
336 [ 'all' => ".efbbbf_bom_char_at_start_of_file {}\n" ],
337 'Leading BOM removed when concatenating files'
338 );
339 }
340
341 /**
342 * @covers ResourceLoaderFileModule::compileLessFile
343 */
344 public function testLessFileCompilation() {
345 $context = $this->getResourceLoaderContext();
346 $basePath = __DIR__ . '/../../data/less/module';
347 $module = new ResourceLoaderFileTestModule( [
348 'localBasePath' => $basePath,
349 'styles' => [ 'styles.less' ],
350 'lessVars' => [ 'foo' => '2px', 'Foo' => '#eeeeee' ]
351 ] );
352 $module->setName( 'test.less' );
353 $styles = $module->getStyles( $context );
354 $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
355 }
356
357 public function provideGetVersionHash() {
358 $a = [];
359 $b = [
360 'lessVars' => [ 'key' => 'value' ],
361 ];
362 yield 'with and without Less variables' => [ $a, $b, false ];
363
364 $a = [
365 'lessVars' => [ 'key' => 'value1' ],
366 ];
367 $b = [
368 'lessVars' => [ 'key' => 'value2' ],
369 ];
370 yield 'different Less variables' => [ $a, $b, false ];
371
372 $x = [
373 'lessVars' => [ 'key' => 'value' ],
374 ];
375 yield 'identical Less variables' => [ $x, $x, true ];
376 }
377
378 /**
379 * @dataProvider provideGetVersionHash
380 * @covers ResourceLoaderFileModule::getDefinitionSummary
381 * @covers ResourceLoaderFileModule::getFileHashes
382 */
383 public function testGetVersionHash( $a, $b, $isEqual ) {
384 $context = $this->getResourceLoaderContext();
385
386 $moduleA = new ResourceLoaderFileTestModule( $a );
387 $versionA = $moduleA->getVersionHash( $context );
388 $moduleB = new ResourceLoaderFileTestModule( $b );
389 $versionB = $moduleB->getVersionHash( $context );
390
391 $this->assertSame(
392 $isEqual,
393 ( $versionA === $versionB ),
394 'Whether versions hashes are equal'
395 );
396 }
397
398 public function provideGetScriptPackageFiles() {
399 $basePath = __DIR__ . '/../../data/resourceloader';
400 $base = [ 'localBasePath' => $basePath ];
401 $commentScript = file_get_contents( "$basePath/script-comment.js" );
402 $nosemiScript = file_get_contents( "$basePath/script-nosemi.js" );
403 $config = RequestContext::getMain()->getConfig();
404 return [
405 [
406 $base + [
407 'packageFiles' => [
408 'script-comment.js',
409 'script-nosemi.js'
410 ]
411 ],
412 [
413 'files' => [
414 'script-comment.js' => [
415 'type' => 'script',
416 'content' => $commentScript,
417 ],
418 'script-nosemi.js' => [
419 'type' => 'script',
420 'content' => $nosemiScript
421 ]
422 ],
423 'main' => 'script-comment.js'
424 ]
425 ],
426 [
427 $base + [
428 'packageFiles' => [
429 'script-comment.js',
430 [ 'name' => 'script-nosemi.js', 'main' => true ]
431 ],
432 'deprecated' => 'Deprecation test',
433 'name' => 'test-deprecated'
434 ],
435 [
436 'files' => [
437 'script-comment.js' => [
438 'type' => 'script',
439 'content' => $commentScript,
440 ],
441 'script-nosemi.js' => [
442 'type' => 'script',
443 'content' => 'mw.log.warn(' .
444 '"This page is using the deprecated ResourceLoader module \"test-deprecated\".\\n' .
445 "Deprecation test" .
446 '");' .
447 $nosemiScript
448 ]
449 ],
450 'main' => 'script-nosemi.js'
451 ]
452 ],
453 [
454 $base + [
455 'packageFiles' => [
456 [ 'name' => 'init.js', 'file' => 'script-comment.js', 'main' => true ],
457 [ 'name' => 'nosemi.js', 'file' => 'script-nosemi.js' ],
458 ]
459 ],
460 [
461 'files' => [
462 'init.js' => [
463 'type' => 'script',
464 'content' => $commentScript,
465 ],
466 'nosemi.js' => [
467 'type' => 'script',
468 'content' => $nosemiScript
469 ]
470 ],
471 'main' => 'init.js'
472 ]
473 ],
474 [
475 $base + [
476 'packageFiles' => [
477 [ 'name' => 'foo.json', 'content' => [ 'Hello' => 'world' ] ],
478 'sample.json',
479 [ 'name' => 'bar.js', 'content' => "console.log('Hello');" ],
480 [ 'name' => 'data.json', 'callback' => function ( $context ) {
481 return [ 'langCode' => $context->getLanguage() ];
482 } ],
483 [ 'name' => 'config.json', 'config' => [
484 'Sitename',
485 'wgVersion' => 'Version',
486 ] ],
487 ]
488 ],
489 [
490 'files' => [
491 'foo.json' => [
492 'type' => 'data',
493 'content' => [ 'Hello' => 'world' ],
494 ],
495 'sample.json' => [
496 'type' => 'data',
497 'content' => (object)[ 'foo' => 'bar', 'answer' => 42 ],
498 ],
499 'bar.js' => [
500 'type' => 'script',
501 'content' => "console.log('Hello');",
502 ],
503 'data.json' => [
504 'type' => 'data',
505 'content' => [ 'langCode' => 'fy' ]
506 ],
507 'config.json' => [
508 'type' => 'data',
509 'content' => [
510 'Sitename' => $config->get( 'Sitename' ),
511 'wgVersion' => $config->get( 'Version' ),
512 ]
513 ]
514 ],
515 'main' => 'bar.js'
516 ],
517 [
518 'lang' => 'fy'
519 ]
520 ],
521 [
522 $base + [
523 'packageFiles' => [
524 [ 'file' => 'script-comment.js' ]
525 ]
526 ],
527 false
528 ],
529 [
530 $base + [
531 'packageFiles' => [
532 [ 'name' => 'foo.json', 'callback' => 'functionThatDoesNotExist142857' ]
533 ]
534 ],
535 false
536 ],
537 [
538 $base + [
539 'packageFiles' => [
540 'foo.json' => [ 'type' => 'script', 'config' => [ 'Sitename' ] ]
541 ]
542 ],
543 false
544 ],
545 [
546 $base + [
547 'packageFiles' => [
548 [ 'name' => 'foo.js', 'config' => 'Sitename' ]
549 ]
550 ],
551 false
552 ],
553 [
554 $base + [
555 'packageFiles' => [
556 'foo.js' => [ 'garbage' => 'data' ]
557 ]
558 ],
559 false
560 ],
561 [
562 $base + [
563 'packageFiles' => [
564 'filethatdoesnotexist142857.js'
565 ]
566 ],
567 false
568 ],
569 [
570 $base + [
571 'packageFiles' => [
572 'script-nosemi.js',
573 [ 'name' => 'foo.json', 'content' => [ 'Hello' => 'world' ], 'main' => true ]
574 ]
575 ],
576 false
577 ]
578 ];
579 }
580
581 /**
582 * @dataProvider provideGetScriptPackageFiles
583 * @covers ResourceLoaderFileModule::getScript
584 * @covers ResourceLoaderFileModule::getPackageFiles
585 * @covers ResourceLoaderFileModule::expandPackageFiles
586 */
587 public function testGetScriptPackageFiles( $moduleDefinition, $expected, $contextOptions = [] ) {
588 $module = new ResourceLoaderFileModule( $moduleDefinition );
589 $context = $this->getResourceLoaderContext( $contextOptions );
590 if ( isset( $moduleDefinition['name'] ) ) {
591 $module->setName( $moduleDefinition['name'] );
592 }
593 if ( $expected === false ) {
594 $this->setExpectedException( MWException::class );
595 $module->getScript( $context );
596 } else {
597 $this->assertEquals( $expected, $module->getScript( $context ) );
598 }
599 }
600 }