1<?php 2 3use Psr\Container\ContainerInterface; 4use Wikimedia\ObjectFactory; 5 6/** 7 * @group ResourceLoader 8 */ 9class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { 10 11 protected function setUp() : void { 12 parent::setUp(); 13 14 $skinFactory = new SkinFactory( new ObjectFactory( 15 $this->createMock( ContainerInterface::class ) 16 ) ); 17 // The empty spec shouldn't matter since this test should never call it 18 $skinFactory->register( 19 'fakeskin', 20 'FakeSkin', 21 [] 22 ); 23 $this->setService( 'SkinFactory', $skinFactory ); 24 25 // This test is not expected to query any database 26 MediaWiki\MediaWikiServices::disableStorageBackend(); 27 } 28 29 private static function getModules() { 30 $base = [ 31 'localBasePath' => __DIR__, 32 ]; 33 34 return [ 35 'noTemplateModule' => [], 36 37 'deprecatedModule' => $base + [ 38 'deprecated' => true, 39 ], 40 'deprecatedTomorrow' => $base + [ 41 'deprecated' => 'Will be removed tomorrow.' 42 ], 43 44 'htmlTemplateModule' => $base + [ 45 'templates' => [ 46 'templates/template.html', 47 'templates/template2.html', 48 ] 49 ], 50 51 'htmlTemplateUnknown' => $base + [ 52 'templates' => [ 53 'templates/notfound.html', 54 ] 55 ], 56 57 'aliasedHtmlTemplateModule' => $base + [ 58 'templates' => [ 59 'foo.html' => 'templates/template.html', 60 'bar.html' => 'templates/template2.html', 61 ] 62 ], 63 64 'templateModuleHandlebars' => $base + [ 65 'templates' => [ 66 'templates/template_awesome.handlebars', 67 ], 68 ], 69 70 'aliasFooFromBar' => $base + [ 71 'templates' => [ 72 'foo.foo' => 'templates/template.bar', 73 ], 74 ], 75 ]; 76 } 77 78 public static function providerTemplateDependencies() { 79 $modules = self::getModules(); 80 81 return [ 82 [ 83 $modules['noTemplateModule'], 84 [], 85 ], 86 [ 87 $modules['htmlTemplateModule'], 88 [ 89 'mediawiki.template', 90 ], 91 ], 92 [ 93 $modules['templateModuleHandlebars'], 94 [ 95 'mediawiki.template', 96 'mediawiki.template.handlebars', 97 ], 98 ], 99 [ 100 $modules['aliasFooFromBar'], 101 [ 102 'mediawiki.template', 103 'mediawiki.template.foo', 104 ], 105 ], 106 ]; 107 } 108 109 /** 110 * @dataProvider providerTemplateDependencies 111 * @covers ResourceLoaderFileModule::__construct 112 * @covers ResourceLoaderFileModule::getDependencies 113 */ 114 public function testTemplateDependencies( $module, $expected ) { 115 $rl = new ResourceLoaderFileModule( $module ); 116 $rl->setName( 'testing' ); 117 $this->assertEquals( $rl->getDependencies(), $expected ); 118 } 119 120 public static function providerDeprecatedModules() { 121 return [ 122 [ 123 'deprecatedModule', 124 'mw.log.warn("This page is using the deprecated ResourceLoader module \"deprecatedModule\".");', 125 ], 126 [ 127 'deprecatedTomorrow', 128 'mw.log.warn(' . 129 '"This page is using the deprecated ResourceLoader module \"deprecatedTomorrow\".\\n' . 130 "Will be removed tomorrow." . 131 '");' 132 ] 133 ]; 134 } 135 136 /** 137 * @dataProvider providerDeprecatedModules 138 * @covers ResourceLoaderFileModule::getScript 139 */ 140 public function testDeprecatedModules( $name, $expected ) { 141 $modules = self::getModules(); 142 $module = new ResourceLoaderFileModule( $modules[$name] ); 143 $module->setName( $name ); 144 $ctx = $this->getResourceLoaderContext(); 145 $this->assertEquals( $module->getScript( $ctx ), $expected ); 146 } 147 148 /** 149 * @covers ResourceLoaderFileModule::getScript 150 * @covers ResourceLoaderFileModule::getScriptFiles 151 * @covers ResourceLoaderFileModule::readScriptFiles 152 */ 153 public function testGetScript() { 154 $module = new ResourceLoaderFileModule( [ 155 'localBasePath' => __DIR__ . '/../../data/resourceloader', 156 'scripts' => [ 'script-nosemi.js', 'script-comment.js' ], 157 ] ); 158 $module->setName( 'testing' ); 159 $ctx = $this->getResourceLoaderContext(); 160 $this->assertEquals( 161 "/* eslint-disable */\nmw.foo()\n" . 162 "\n" . 163 "/* eslint-disable */\nmw.foo()\n// mw.bar();\n" . 164 "\n", 165 $module->getScript( $ctx ), 166 'scripts are concatenated with a new-line' 167 ); 168 } 169 170 /** 171 * @covers ResourceLoaderFileModule 172 */ 173 public function testGetAllSkinStyleFiles() { 174 $baseParams = [ 175 'scripts' => [ 176 'foo.js', 177 'bar.js', 178 ], 179 'styles' => [ 180 'foo.css', 181 'bar.css' => [ 'media' => 'print' ], 182 'screen.less' => [ 'media' => 'screen' ], 183 'screen-query.css' => [ 'media' => 'screen and (min-width: 400px)' ], 184 ], 185 'skinStyles' => [ 186 'default' => 'quux-fallback.less', 187 'fakeskin' => [ 188 'baz-vector.css', 189 'quux-vector.less', 190 ], 191 ], 192 'messages' => [ 193 'hello', 194 'world', 195 ], 196 ]; 197 198 $module = new ResourceLoaderFileModule( $baseParams ); 199 $module->setName( 'testing' ); 200 201 $this->assertEquals( 202 [ 203 'foo.css', 204 'baz-vector.css', 205 'quux-vector.less', 206 'quux-fallback.less', 207 'bar.css', 208 'screen.less', 209 'screen-query.css', 210 ], 211 array_map( 'basename', $module->getAllStyleFiles() ) 212 ); 213 } 214 215 /** 216 * Strip @noflip annotations from CSS code. 217 * @param string $css 218 * @return string 219 */ 220 private static function stripNoflip( $css ) { 221 return str_replace( '/*@noflip*/ ', '', $css ); 222 } 223 224 /** 225 * What happens when you mix @embed and @noflip? 226 * This really is an integration test, but oh well. 227 * 228 * @covers ResourceLoaderFileModule::getStyles 229 * @covers ResourceLoaderFileModule::getStyleFiles 230 * @covers ResourceLoaderFileModule::readStyleFiles 231 * @covers ResourceLoaderFileModule::readStyleFile 232 */ 233 public function testMixedCssAnnotations() { 234 $basePath = __DIR__ . '/../../data/css'; 235 $testModule = new ResourceLoaderFileTestModule( [ 236 'localBasePath' => $basePath, 237 'styles' => [ 'test.css' ], 238 ] ); 239 $testModule->setName( 'testing' ); 240 $expectedModule = new ResourceLoaderFileTestModule( [ 241 'localBasePath' => $basePath, 242 'styles' => [ 'expected.css' ], 243 ] ); 244 $expectedModule->setName( 'testing' ); 245 246 $contextLtr = $this->getResourceLoaderContext( [ 247 'lang' => 'en', 248 'dir' => 'ltr', 249 ] ); 250 $contextRtl = $this->getResourceLoaderContext( [ 251 'lang' => 'he', 252 'dir' => 'rtl', 253 ] ); 254 255 // Since we want to compare the effect of @noflip+@embed against the effect of just @embed, and 256 // the @noflip annotations are always preserved, we need to strip them first. 257 $this->assertEquals( 258 $expectedModule->getStyles( $contextLtr ), 259 self::stripNoflip( $testModule->getStyles( $contextLtr ) ), 260 "/*@noflip*/ with /*@embed*/ gives correct results in LTR mode" 261 ); 262 $this->assertEquals( 263 $expectedModule->getStyles( $contextLtr ), 264 self::stripNoflip( $testModule->getStyles( $contextRtl ) ), 265 "/*@noflip*/ with /*@embed*/ gives correct results in RTL mode" 266 ); 267 } 268 269 /** 270 * @covers ResourceLoaderFileModule 271 */ 272 public function testCssFlipping() { 273 $plain = new ResourceLoaderFileTestModule( [ 274 'localBasePath' => __DIR__ . '/../../data/resourceloader', 275 'styles' => [ 'direction.css' ], 276 ] ); 277 $plain->setName( 'test' ); 278 279 $context = $this->getResourceLoaderContext( [ 'lang' => 'en', 'dir' => 'ltr' ] ); 280 $this->assertEquals( 281 $plain->getStyles( $context ), 282 [ 'all' => ".example { text-align: left; }\n" ], 283 'Unchanged styles in LTR mode' 284 ); 285 $context = $this->getResourceLoaderContext( [ 'lang' => 'he', 'dir' => 'rtl' ] ); 286 $this->assertEquals( 287 $plain->getStyles( $context ), 288 [ 'all' => ".example { text-align: right; }\n" ], 289 'Flipped styles in RTL mode' 290 ); 291 292 $noflip = new ResourceLoaderFileTestModule( [ 293 'localBasePath' => __DIR__ . '/../../data/resourceloader', 294 'styles' => [ 'direction.css' ], 295 'noflip' => true, 296 ] ); 297 $noflip->setName( 'test' ); 298 $this->assertEquals( 299 $plain->getStyles( $context ), 300 [ 'all' => ".example { text-align: right; }\n" ], 301 'Unchanged styles in RTL mode with noflip at module level' 302 ); 303 } 304 305 /** 306 * Test reading files from elsewhere than localBasePath using ResourceLoaderFilePath. 307 * 308 * This mimics modules modified by skins using 'ResourceModuleSkinStyles' and 'OOUIThemePaths' 309 * skin attributes. 310 * 311 * @covers ResourceLoaderFilePath::getLocalBasePath 312 * @covers ResourceLoaderFilePath::getRemoteBasePath 313 */ 314 public function testResourceLoaderFilePath() { 315 $basePath = __DIR__ . '/../../data/blahblah'; 316 $filePath = __DIR__ . '/../../data/rlfilepath'; 317 $testModule = new ResourceLoaderFileModule( [ 318 'localBasePath' => $basePath, 319 'remoteBasePath' => 'blahblah', 320 'styles' => new ResourceLoaderFilePath( 'style.css', $filePath, 'rlfilepath' ), 321 'skinStyles' => [ 322 'vector' => new ResourceLoaderFilePath( 'skinStyle.css', $filePath, 'rlfilepath' ), 323 ], 324 'scripts' => new ResourceLoaderFilePath( 'script.js', $filePath, 'rlfilepath' ), 325 'templates' => new ResourceLoaderFilePath( 'template.html', $filePath, 'rlfilepath' ), 326 ] ); 327 $expectedModule = new ResourceLoaderFileModule( [ 328 'localBasePath' => $filePath, 329 'remoteBasePath' => 'rlfilepath', 330 'styles' => 'style.css', 331 'skinStyles' => [ 332 'vector' => 'skinStyle.css', 333 ], 334 'scripts' => 'script.js', 335 'templates' => 'template.html', 336 ] ); 337 338 $context = $this->getResourceLoaderContext(); 339 $this->assertEquals( 340 $expectedModule->getModuleContent( $context ), 341 $testModule->getModuleContent( $context ), 342 "Using ResourceLoaderFilePath works correctly" 343 ); 344 } 345 346 public static function providerGetTemplates() { 347 $modules = self::getModules(); 348 349 return [ 350 [ 351 $modules['noTemplateModule'], 352 [], 353 ], 354 [ 355 $modules['templateModuleHandlebars'], 356 [ 357 'templates/template_awesome.handlebars' => "wow\n", 358 ], 359 ], 360 [ 361 $modules['htmlTemplateModule'], 362 [ 363 'templates/template.html' => "<strong>hello</strong>\n", 364 'templates/template2.html' => "<div>goodbye</div>\n", 365 ], 366 ], 367 [ 368 $modules['aliasedHtmlTemplateModule'], 369 [ 370 'foo.html' => "<strong>hello</strong>\n", 371 'bar.html' => "<div>goodbye</div>\n", 372 ], 373 ], 374 [ 375 $modules['htmlTemplateUnknown'], 376 false, 377 ], 378 ]; 379 } 380 381 /** 382 * @dataProvider providerGetTemplates 383 * @covers ResourceLoaderFileModule::getTemplates 384 */ 385 public function testGetTemplates( $module, $expected ) { 386 $rl = new ResourceLoaderFileModule( $module ); 387 $rl->setName( 'testing' ); 388 389 if ( $expected === false ) { 390 $this->expectException( RuntimeException::class ); 391 $rl->getTemplates(); 392 } else { 393 $this->assertEquals( $rl->getTemplates(), $expected ); 394 } 395 } 396 397 /** 398 * @covers ResourceLoaderFileModule::stripBom 399 */ 400 public function testBomConcatenation() { 401 $basePath = __DIR__ . '/../../data/css'; 402 $testModule = new ResourceLoaderFileTestModule( [ 403 'localBasePath' => $basePath, 404 'styles' => [ 'bom.css' ], 405 ] ); 406 $testModule->setName( 'testing' ); 407 $this->assertEquals( 408 substr( file_get_contents( "$basePath/bom.css" ), 0, 10 ), 409 "\xef\xbb\xbf.efbbbf", 410 'File has leading BOM' 411 ); 412 413 $context = $this->getResourceLoaderContext(); 414 $this->assertEquals( 415 $testModule->getStyles( $context ), 416 [ 'all' => ".efbbbf_bom_char_at_start_of_file {}\n" ], 417 'Leading BOM removed when concatenating files' 418 ); 419 } 420 421 /** 422 * @covers ResourceLoaderFileModule 423 */ 424 public function testLessFileCompilation() { 425 $context = $this->getResourceLoaderContext(); 426 $basePath = __DIR__ . '/../../data/less/module'; 427 $module = new ResourceLoaderFileTestModule( [ 428 'localBasePath' => $basePath, 429 'styles' => [ 'styles.less' ], 430 'lessVars' => [ 'foo' => '2px', 'Foo' => '#eeeeee' ] 431 ] ); 432 $module->setName( 'test.less' ); 433 $styles = $module->getStyles( $context ); 434 $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] ); 435 } 436 437 public function provideGetVersionHash() { 438 $a = []; 439 $b = [ 440 'lessVars' => [ 'key' => 'value' ], 441 ]; 442 yield 'with and without Less variables' => [ $a, $b, false ]; 443 444 $a = [ 445 'lessVars' => [ 'key' => 'value1' ], 446 ]; 447 $b = [ 448 'lessVars' => [ 'key' => 'value2' ], 449 ]; 450 yield 'different Less variables' => [ $a, $b, false ]; 451 452 $x = [ 453 'lessVars' => [ 'key' => 'value' ], 454 ]; 455 yield 'identical Less variables' => [ $x, $x, true ]; 456 457 $a = [ 458 'packageFiles' => [ [ 'name' => 'data.json', 'callback' => function () { 459 return [ 'aaa' ]; 460 } ] ] 461 ]; 462 $b = [ 463 'packageFiles' => [ [ 'name' => 'data.json', 'callback' => function () { 464 return [ 'bbb' ]; 465 } ] ] 466 ]; 467 yield 'packageFiles with different callback' => [ $a, $b, false ]; 468 469 $a = [ 470 'packageFiles' => [ [ 'name' => 'aaa.json', 'callback' => function () { 471 return [ 'x' ]; 472 } ] ] 473 ]; 474 $b = [ 475 'packageFiles' => [ [ 'name' => 'bbb.json', 'callback' => function () { 476 return [ 'x' ]; 477 } ] ] 478 ]; 479 yield 'packageFiles with different file name and a callback' => [ $a, $b, false ]; 480 481 $a = [ 482 'packageFiles' => [ [ 'name' => 'data.json', 'versionCallback' => function () { 483 return [ 'A-version' ]; 484 }, 'callback' => function () { 485 throw new Exception( 'Unexpected computation' ); 486 } ] ] 487 ]; 488 $b = [ 489 'packageFiles' => [ [ 'name' => 'data.json', 'versionCallback' => function () { 490 return [ 'B-version' ]; 491 }, 'callback' => function () { 492 throw new Exception( 'Unexpected computation' ); 493 } ] ] 494 ]; 495 yield 'packageFiles with different versionCallback' => [ $a, $b, false ]; 496 497 $a = [ 498 'packageFiles' => [ [ 'name' => 'aaa.json', 499 'versionCallback' => function () { 500 return [ 'X-version' ]; 501 }, 502 'callback' => function () { 503 throw new Exception( 'Unexpected computation' ); 504 } 505 ] ] 506 ]; 507 $b = [ 508 'packageFiles' => [ [ 'name' => 'bbb.json', 509 'versionCallback' => function () { 510 return [ 'X-version' ]; 511 }, 512 'callback' => function () { 513 throw new Exception( 'Unexpected computation' ); 514 } 515 ] ] 516 ]; 517 yield 'packageFiles with different file name and a versionCallback' => [ $a, $b, false ]; 518 } 519 520 /** 521 * @dataProvider provideGetVersionHash 522 * @covers ResourceLoaderFileModule::getDefinitionSummary 523 * @covers ResourceLoaderFileModule::getFileHashes 524 */ 525 public function testGetVersionHash( $a, $b, $isEqual ) { 526 $context = $this->getResourceLoaderContext(); 527 528 $moduleA = new ResourceLoaderFileTestModule( $a ); 529 $versionA = $moduleA->getVersionHash( $context ); 530 $moduleB = new ResourceLoaderFileTestModule( $b ); 531 $versionB = $moduleB->getVersionHash( $context ); 532 533 $this->assertSame( 534 $isEqual, 535 ( $versionA === $versionB ), 536 'Whether versions hashes are equal' 537 ); 538 } 539 540 public function provideGetScriptPackageFiles() { 541 $basePath = __DIR__ . '/../../data/resourceloader'; 542 $base = [ 'localBasePath' => $basePath ]; 543 $commentScript = file_get_contents( "$basePath/script-comment.js" ); 544 $nosemiScript = file_get_contents( "$basePath/script-nosemi.js" ); 545 $vueComponentDebug = trim( file_get_contents( "$basePath/vue-component-output-debug.js.txt" ) ); 546 $vueComponentNonDebug = trim( file_get_contents( "$basePath/vue-component-output-nondebug.js.txt" ) ); 547 $config = RequestContext::getMain()->getConfig(); 548 return [ 549 [ 550 $base + [ 551 'packageFiles' => [ 552 'script-comment.js', 553 'script-nosemi.js' 554 ] 555 ], 556 [ 557 'files' => [ 558 'script-comment.js' => [ 559 'type' => 'script', 560 'content' => $commentScript, 561 ], 562 'script-nosemi.js' => [ 563 'type' => 'script', 564 'content' => $nosemiScript 565 ] 566 ], 567 'main' => 'script-comment.js' 568 ] 569 ], 570 [ 571 $base + [ 572 'packageFiles' => [ 573 'script-comment.js', 574 [ 'name' => 'script-nosemi.js', 'main' => true ] 575 ], 576 'deprecated' => 'Deprecation test', 577 'name' => 'test-deprecated' 578 ], 579 [ 580 'files' => [ 581 'script-comment.js' => [ 582 'type' => 'script', 583 'content' => $commentScript, 584 ], 585 'script-nosemi.js' => [ 586 'type' => 'script', 587 'content' => 'mw.log.warn(' . 588 '"This page is using the deprecated ResourceLoader module \"test-deprecated\".\\n' . 589 "Deprecation test" . 590 '");' . 591 $nosemiScript 592 ] 593 ], 594 'main' => 'script-nosemi.js' 595 ] 596 ], 597 [ 598 $base + [ 599 'packageFiles' => [ 600 [ 'name' => 'init.js', 'file' => 'script-comment.js', 'main' => true ], 601 [ 'name' => 'nosemi.js', 'file' => 'script-nosemi.js' ], 602 ] 603 ], 604 [ 605 'files' => [ 606 'init.js' => [ 607 'type' => 'script', 608 'content' => $commentScript, 609 ], 610 'nosemi.js' => [ 611 'type' => 'script', 612 'content' => $nosemiScript 613 ] 614 ], 615 'main' => 'init.js' 616 ] 617 ], 618 'package file with callback' => [ 619 $base + [ 620 'packageFiles' => [ 621 [ 'name' => 'foo.json', 'content' => [ 'Hello' => 'world' ] ], 622 'sample.json', 623 [ 'name' => 'bar.js', 'content' => "console.log('Hello');" ], 624 [ 625 'name' => 'data.json', 626 'callback' => function ( $context, $config, $extra ) { 627 return [ 'langCode' => $context->getLanguage(), 'extra' => $extra ]; 628 }, 629 'callbackParam' => [ 'a' => 'b' ], 630 ], 631 [ 'name' => 'config.json', 'config' => [ 632 'Sitename', 633 'server' => 'ServerName', 634 ] ], 635 ] 636 ], 637 [ 638 'files' => [ 639 'foo.json' => [ 640 'type' => 'data', 641 'content' => [ 'Hello' => 'world' ], 642 ], 643 'sample.json' => [ 644 'type' => 'data', 645 'content' => (object)[ 'foo' => 'bar', 'answer' => 42 ], 646 ], 647 'bar.js' => [ 648 'type' => 'script', 649 'content' => "console.log('Hello');", 650 ], 651 'data.json' => [ 652 'type' => 'data', 653 'content' => [ 'langCode' => 'fy', 'extra' => [ 'a' => 'b' ] ], 654 ], 655 'config.json' => [ 656 'type' => 'data', 657 'content' => [ 658 'Sitename' => $config->get( 'Sitename' ), 659 'server' => $config->get( 'ServerName' ), 660 ] 661 ] 662 ], 663 'main' => 'bar.js' 664 ], 665 [ 666 'lang' => 'fy' 667 ] 668 ], 669 'package file with callback and versionCallback' => [ 670 $base + [ 671 'packageFiles' => [ 672 [ 'name' => 'bar.js', 'content' => "console.log('Hello');" ], 673 [ 674 'name' => 'data.json', 675 'versionCallback' => function ( $context ) { 676 return 'x'; 677 }, 678 'callback' => function ( $context, $config, $extra ) { 679 return [ 'langCode' => $context->getLanguage(), 'extra' => $extra ]; 680 }, 681 'callbackParam' => [ 'A', 'B' ] 682 ], 683 ] 684 ], 685 [ 686 'files' => [ 687 'bar.js' => [ 688 'type' => 'script', 689 'content' => "console.log('Hello');", 690 ], 691 'data.json' => [ 692 'type' => 'data', 693 'content' => [ 'langCode' => 'fy', 'extra' => [ 'A', 'B' ] ], 694 ], 695 ], 696 'main' => 'bar.js' 697 ], 698 [ 699 'lang' => 'fy' 700 ] 701 ], 702 'package file with callback that returns a file (1)' => [ 703 $base + [ 704 'packageFiles' => [ 705 [ 'name' => 'dynamic.js', 'callback' => function ( $context ) { 706 $file = $context->getLanguage() === 'fy' ? 'script-comment.js' : 'script-nosemi.js'; 707 return new ResourceLoaderFilePath( $file ); 708 } ] 709 ] 710 ], 711 [ 712 'files' => [ 713 'dynamic.js' => [ 714 'type' => 'script', 715 'content' => $commentScript, 716 ] 717 ], 718 'main' => 'dynamic.js' 719 ], 720 [ 721 'lang' => 'fy' 722 ] 723 ], 724 'package file with callback that returns a file (2)' => [ 725 $base + [ 726 'packageFiles' => [ 727 [ 'name' => 'dynamic.js', 'callback' => function ( $context ) { 728 $file = $context->getLanguage() === 'fy' ? 'script-comment.js' : 'script-nosemi.js'; 729 return new ResourceLoaderFilePath( $file ); 730 } ] 731 ] 732 ], 733 [ 734 'files' => [ 735 'dynamic.js' => [ 736 'type' => 'script', 737 'content' => $nosemiScript, 738 ] 739 ], 740 'main' => 'dynamic.js' 741 ], 742 [ 743 'lang' => 'nl' 744 ] 745 ], 746 '.vue file in debug mode' => [ 747 $base + [ 748 'packageFiles' => [ 749 'vue-component.vue' 750 ] 751 ], 752 [ 753 'files' => [ 754 'vue-component.vue' => [ 755 'type' => 'script', 756 'content' => $vueComponentDebug 757 ] 758 ], 759 'main' => 'vue-component.vue', 760 ], 761 [ 762 'debug' => 'true' 763 ] 764 ], 765 '.vue file in non-debug mode' => [ 766 $base + [ 767 'packageFiles' => [ 768 'vue-component.vue' 769 ], 770 'name' => 'nondebug', 771 ], 772 [ 773 'files' => [ 774 'vue-component.vue' => [ 775 'type' => 'script', 776 'content' => $vueComponentNonDebug 777 ] 778 ], 779 'main' => 'vue-component.vue' 780 ], 781 [ 782 'debug' => 'false' 783 ] 784 ], 785 [ 786 $base + [ 787 'packageFiles' => [ 788 [ 'file' => 'script-comment.js' ] 789 ] 790 ], 791 LogicException::class 792 ], 793 'package file with invalid callback' => [ 794 $base + [ 795 'packageFiles' => [ 796 [ 'name' => 'foo.json', 'callback' => 'functionThatDoesNotExist142857' ] 797 ] 798 ], 799 LogicException::class 800 ], 801 [ 802 // 'config' not valid for 'script' type 803 $base + [ 804 'packageFiles' => [ 805 'foo.json' => [ 'type' => 'script', 'config' => [ 'Sitename' ] ] 806 ] 807 ], 808 LogicException::class 809 ], 810 [ 811 // 'config' not valid for '*.js' file 812 $base + [ 813 'packageFiles' => [ 814 [ 'name' => 'foo.js', 'config' => 'Sitename' ] 815 ] 816 ], 817 LogicException::class 818 ], 819 [ 820 // missing type/name/file. 821 $base + [ 822 'packageFiles' => [ 823 'foo.js' => [ 'garbage' => 'data' ] 824 ] 825 ], 826 LogicException::class 827 ], 828 [ 829 $base + [ 830 'packageFiles' => [ 831 'filethatdoesnotexist142857.js' 832 ] 833 ], 834 RuntimeException::class 835 ], 836 [ 837 // JSON can't be a main file 838 $base + [ 839 'packageFiles' => [ 840 'script-nosemi.js', 841 [ 'name' => 'foo.json', 'content' => [ 'Hello' => 'world' ], 'main' => true ] 842 ] 843 ], 844 LogicException::class 845 ] 846 ]; 847 } 848 849 /** 850 * @dataProvider provideGetScriptPackageFiles 851 * @covers ResourceLoaderFileModule::getScript 852 * @covers ResourceLoaderFileModule::getPackageFiles 853 * @covers ResourceLoaderFileModule::expandPackageFiles 854 */ 855 public function testGetScriptPackageFiles( $moduleDefinition, $expected, $contextOptions = [] ) { 856 $module = new ResourceLoaderFileModule( $moduleDefinition ); 857 $context = $this->getResourceLoaderContext( $contextOptions ); 858 if ( isset( $moduleDefinition['name'] ) ) { 859 $module->setName( $moduleDefinition['name'] ); 860 } 861 if ( is_string( $expected ) ) { 862 // Class name of expected exception 863 $this->expectException( $expected ); 864 $module->getScript( $context ); 865 } else { 866 // Array of expected return value 867 $this->assertEquals( $expected, $module->getScript( $context ) ); 868 } 869 } 870} 871