5 * @group ResourceLoader
7 class ResourceLoaderFileModuleTest
extends ResourceLoaderTestCase
{
9 protected function setUp() {
12 $skinFactory = new SkinFactory();
13 // The return value of the closure shouldn't matter since this test should
15 $skinFactory->register(
21 $this->setService( 'SkinFactory', $skinFactory );
24 private static function getModules() {
26 'localBasePath' => realpath( __DIR__
),
30 'noTemplateModule' => [],
32 'deprecatedModule' => $base +
[
35 'deprecatedTomorrow' => $base +
[
36 'deprecated' => 'Will be removed tomorrow.'
39 'htmlTemplateModule' => $base +
[
41 'templates/template.html',
42 'templates/template2.html',
46 'htmlTemplateUnknown' => $base +
[
48 'templates/notfound.html',
52 'aliasedHtmlTemplateModule' => $base +
[
54 'foo.html' => 'templates/template.html',
55 'bar.html' => 'templates/template2.html',
59 'templateModuleHandlebars' => $base +
[
61 'templates/template_awesome.handlebars',
65 'aliasFooFromBar' => $base +
[
67 'foo.foo' => 'templates/template.bar',
73 public static function providerTemplateDependencies() {
74 $modules = self
::getModules();
78 $modules['noTemplateModule'],
82 $modules['htmlTemplateModule'],
88 $modules['templateModuleHandlebars'],
91 'mediawiki.template.handlebars',
95 $modules['aliasFooFromBar'],
98 'mediawiki.template.foo',
105 * @dataProvider providerTemplateDependencies
106 * @covers ResourceLoaderFileModule::__construct
107 * @covers ResourceLoaderFileModule::getDependencies
109 public function testTemplateDependencies( $module, $expected ) {
110 $rl = new ResourceLoaderFileModule( $module );
111 $rl->setName( 'testing' );
112 $this->assertEquals( $rl->getDependencies(), $expected );
115 public static function providerDeprecatedModules() {
119 'mw.log.warn("This page is using the deprecated ResourceLoader module \"deprecatedModule\".");',
122 'deprecatedTomorrow',
124 '"This page is using the deprecated ResourceLoader module \"deprecatedTomorrow\".\\n' .
125 "Will be removed tomorrow." .
132 * @dataProvider providerDeprecatedModules
133 * @covers ResourceLoaderFileModule::getScript
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 );
144 * @covers ResourceLoaderFileModule::getScript
145 * @covers ResourceLoaderFileModule::getScriptFiles
146 * @covers ResourceLoaderFileModule::readScriptFiles
148 public function testGetScript() {
149 $module = new ResourceLoaderFileModule( [
150 'localBasePath' => __DIR__
. '/../../data/resourceloader',
151 'scripts' => [ 'script-nosemi.js', 'script-comment.js' ],
153 $module->setName( 'testing' );
154 $ctx = $this->getResourceLoaderContext();
156 "/* eslint-disable */\nmw.foo()\n" .
158 "/* eslint-disable */\nmw.foo()\n// mw.bar();\n" .
160 $module->getScript( $ctx ),
161 'scripts are concatenated with a new-line'
166 * @covers ResourceLoaderFileModule::getAllStyleFiles
167 * @covers ResourceLoaderFileModule::getAllSkinStyleFiles
168 * @covers ResourceLoaderFileModule::getSkinStyleFiles
170 public function testGetAllSkinStyleFiles() {
178 'bar.css' => [ 'media' => 'print' ],
179 'screen.less' => [ 'media' => 'screen' ],
180 'screen-query.css' => [ 'media' => 'screen and (min-width: 400px)' ],
183 'default' => 'quux-fallback.less',
195 $module = new ResourceLoaderFileModule( $baseParams );
196 $module->setName( 'testing' );
203 'quux-fallback.less',
208 array_map( 'basename', $module->getAllStyleFiles() )
213 * Strip @noflip annotations from CSS code.
217 private static function stripNoflip( $css ) {
218 return str_replace( '/*@noflip*/ ', '', $css );
222 * What happens when you mix @embed and @noflip?
223 * This really is an integration test, but oh well.
225 * @covers ResourceLoaderFileModule::getStyles
226 * @covers ResourceLoaderFileModule::getStyleFiles
227 * @covers ResourceLoaderFileModule::readStyleFiles
228 * @covers ResourceLoaderFileModule::readStyleFile
230 public function testMixedCssAnnotations() {
231 $basePath = __DIR__
. '/../../data/css';
232 $testModule = new ResourceLoaderFileModule( [
233 'localBasePath' => $basePath,
234 'styles' => [ 'test.css' ],
236 $testModule->setName( 'testing' );
237 $expectedModule = new ResourceLoaderFileModule( [
238 'localBasePath' => $basePath,
239 'styles' => [ 'expected.css' ],
241 $expectedModule->setName( 'testing' );
243 $contextLtr = $this->getResourceLoaderContext( [
247 $contextRtl = $this->getResourceLoaderContext( [
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.
255 $expectedModule->getStyles( $contextLtr ),
256 self
::stripNoflip( $testModule->getStyles( $contextLtr ) ),
257 "/*@noflip*/ with /*@embed*/ gives correct results in LTR mode"
260 $expectedModule->getStyles( $contextLtr ),
261 self
::stripNoflip( $testModule->getStyles( $contextRtl ) ),
262 "/*@noflip*/ with /*@embed*/ gives correct results in RTL mode"
266 public static function providerGetTemplates() {
267 $modules = self
::getModules();
271 $modules['noTemplateModule'],
275 $modules['templateModuleHandlebars'],
277 'templates/template_awesome.handlebars' => "wow\n",
281 $modules['htmlTemplateModule'],
283 'templates/template.html' => "<strong>hello</strong>\n",
284 'templates/template2.html' => "<div>goodbye</div>\n",
288 $modules['aliasedHtmlTemplateModule'],
290 'foo.html' => "<strong>hello</strong>\n",
291 'bar.html' => "<div>goodbye</div>\n",
295 $modules['htmlTemplateUnknown'],
302 * @dataProvider providerGetTemplates
303 * @covers ResourceLoaderFileModule::getTemplates
305 public function testGetTemplates( $module, $expected ) {
306 $rl = new ResourceLoaderFileModule( $module );
307 $rl->setName( 'testing' );
309 if ( $expected === false ) {
310 $this->setExpectedException( MWException
::class );
313 $this->assertEquals( $rl->getTemplates(), $expected );
318 * @covers ResourceLoaderFileModule::stripBom
320 public function testBomConcatenation() {
321 $basePath = __DIR__
. '/../../data/css';
322 $testModule = new ResourceLoaderFileModule( [
323 'localBasePath' => $basePath,
324 'styles' => [ 'bom.css' ],
326 $testModule->setName( 'testing' );
328 substr( file_get_contents( "$basePath/bom.css" ), 0, 10 ),
329 "\xef\xbb\xbf.efbbbf",
330 'File has leading BOM'
333 $context = $this->getResourceLoaderContext();
335 $testModule->getStyles( $context ),
336 [ 'all' => ".efbbbf_bom_char_at_start_of_file {}\n" ],
337 'Leading BOM removed when concatenating files'
342 * @covers ResourceLoaderFileModule::compileLessFile
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' ]
352 $module->setName( 'test.less' );
353 $styles = $module->getStyles( $context );
354 $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
357 public function provideGetVersionHash() {
360 'lessVars' => [ 'key' => 'value' ],
362 yield
'with and without Less variables' => [ $a, $b, false ];
365 'lessVars' => [ 'key' => 'value1' ],
368 'lessVars' => [ 'key' => 'value2' ],
370 yield
'different Less variables' => [ $a, $b, false ];
373 'lessVars' => [ 'key' => 'value' ],
375 yield
'identical Less variables' => [ $x, $x, true ];
379 * @dataProvider provideGetVersionHash
380 * @covers ResourceLoaderFileModule::getDefinitionSummary
381 * @covers ResourceLoaderFileModule::getFileHashes
383 public function testGetVersionHash( $a, $b, $isEqual ) {
384 $context = $this->getResourceLoaderContext();
386 $moduleA = new ResourceLoaderFileTestModule( $a );
387 $versionA = $moduleA->getVersionHash( $context );
388 $moduleB = new ResourceLoaderFileTestModule( $b );
389 $versionB = $moduleB->getVersionHash( $context );
393 ( $versionA === $versionB ),
394 'Whether versions hashes are equal'
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();
414 'script-comment.js' => [
416 'content' => $commentScript,
418 'script-nosemi.js' => [
420 'content' => $nosemiScript
423 'main' => 'script-comment.js'
430 [ 'name' => 'script-nosemi.js', 'main' => true ]
432 'deprecated' => 'Deprecation test',
433 'name' => 'test-deprecated'
437 'script-comment.js' => [
439 'content' => $commentScript,
441 'script-nosemi.js' => [
443 'content' => 'mw.log.warn(' .
444 '"This page is using the deprecated ResourceLoader module \"test-deprecated\".\\n' .
450 'main' => 'script-nosemi.js'
456 [ 'name' => 'init.js', 'file' => 'script-comment.js', 'main' => true ],
457 [ 'name' => 'nosemi.js', 'file' => 'script-nosemi.js' ],
464 'content' => $commentScript,
468 'content' => $nosemiScript
477 [ 'name' => 'foo.json', 'content' => [ 'Hello' => 'world' ] ],
479 [ 'name' => 'bar.js', 'content' => "console.log('Hello');" ],
480 [ 'name' => 'data.json', 'callback' => function ( $context ) {
481 return [ 'langCode' => $context->getLanguage() ];
483 [ 'name' => 'config.json', 'config' => [
485 'wgVersion' => 'Version',
493 'content' => [ 'Hello' => 'world' ],
497 'content' => (object)[ 'foo' => 'bar', 'answer' => 42 ],
501 'content' => "console.log('Hello');",
505 'content' => [ 'langCode' => 'fy' ]
510 'Sitename' => $config->get( 'Sitename' ),
511 'wgVersion' => $config->get( 'Version' ),
524 [ 'file' => 'script-comment.js' ]
532 [ 'name' => 'foo.json', 'callback' => 'functionThatDoesNotExist142857' ]
540 'foo.json' => [ 'type' => 'script', 'config' => [ 'Sitename' ] ]
548 [ 'name' => 'foo.js', 'config' => 'Sitename' ]
556 'foo.js' => [ 'garbage' => 'data' ]
564 'filethatdoesnotexist142857.js'
573 [ 'name' => 'foo.json', 'content' => [ 'Hello' => 'world' ], 'main' => true ]
582 * @dataProvider provideGetScriptPackageFiles
583 * @covers ResourceLoaderFileModule::getScript
584 * @covers ResourceLoaderFileModule::getPackageFiles
585 * @covers ResourceLoaderFileModule::expandPackageFiles
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'] );
593 if ( $expected === false ) {
594 $this->setExpectedException( MWException
::class );
595 $module->getScript( $context );
597 $this->assertEquals( $expected, $module->getScript( $context ) );