3 use Wikimedia\TestingAccessWrapper
;
5 class ResourceLoaderTest
extends ResourceLoaderTestCase
{
7 protected function setUp() {
10 $this->setMwGlobals( [
11 'wgResourceLoaderLESSImportPaths' => [],
12 'wgResourceLoaderLESSVars' => [
17 // Clear ResourceLoaderGetConfigVars hooks (called by StartupModule)
18 // to avoid notices during testMakeModuleResponse for missing
19 // wgResourceLoaderLESSVars keys in extension hooks.
21 'wgShowExceptionDetails' => true,
26 * Ensure the ResourceLoaderRegisterModules hook is called.
28 * @covers ResourceLoader::__construct
30 public function testConstructRegistrationHook() {
31 $resourceLoaderRegisterModulesHook = false;
33 $this->setMwGlobals( 'wgHooks', [
34 'ResourceLoaderRegisterModules' => [
35 function ( &$resourceLoader ) use ( &$resourceLoaderRegisterModulesHook ) {
36 $resourceLoaderRegisterModulesHook = true;
41 $unused = new ResourceLoader();
43 $resourceLoaderRegisterModulesHook,
44 'Hook ResourceLoaderRegisterModules called'
49 * @covers ResourceLoader::register
50 * @covers ResourceLoader::getModule
52 public function testRegisterValidObject() {
53 $module = new ResourceLoaderTestModule();
54 $resourceLoader = new EmptyResourceLoader();
55 $resourceLoader->register( 'test', $module );
56 $this->assertEquals( $module, $resourceLoader->getModule( 'test' ) );
60 * @covers ResourceLoader::register
61 * @covers ResourceLoader::getModule
63 public function testRegisterValidArray() {
64 $module = new ResourceLoaderTestModule();
65 $resourceLoader = new EmptyResourceLoader();
66 // Covers case of register() setting $rl->moduleInfos,
67 // but $rl->modules lazy-populated by getModule()
68 $resourceLoader->register( 'test', [ 'object' => $module ] );
69 $this->assertEquals( $module, $resourceLoader->getModule( 'test' ) );
73 * @covers ResourceLoader::register
75 public function testRegisterEmptyString() {
76 $module = new ResourceLoaderTestModule();
77 $resourceLoader = new EmptyResourceLoader();
78 $resourceLoader->register( '', $module );
79 $this->assertEquals( $module, $resourceLoader->getModule( '' ) );
83 * @covers ResourceLoader::register
85 public function testRegisterInvalidName() {
86 $resourceLoader = new EmptyResourceLoader();
87 $this->setExpectedException( MWException
::class, "name 'test!invalid' is invalid" );
88 $resourceLoader->register( 'test!invalid', new ResourceLoaderTestModule() );
92 * @covers ResourceLoader::register
94 public function testRegisterInvalidType() {
95 $resourceLoader = new EmptyResourceLoader();
96 $this->setExpectedException( MWException
::class, 'ResourceLoader module info type error' );
97 $resourceLoader->register( 'test', new stdClass() );
101 * @covers ResourceLoader::register
103 public function testRegisterDuplicate() {
104 $logger = $this->getMockBuilder( Psr\Log\LoggerInterface
::class )->getMock();
105 $logger->expects( $this->once() )
106 ->method( 'warning' );
107 $resourceLoader = new EmptyResourceLoader( null, $logger );
109 $module1 = new ResourceLoaderTestModule();
110 $module2 = new ResourceLoaderTestModule();
111 $resourceLoader->register( 'test', $module1 );
112 $resourceLoader->register( 'test', $module2 );
113 $this->assertSame( $module2, $resourceLoader->getModule( 'test' ) );
117 * @covers ResourceLoader::getModuleNames
119 public function testGetModuleNames() {
120 // Use an empty one so that core and extension modules don't get in.
121 $resourceLoader = new EmptyResourceLoader();
122 $resourceLoader->register( 'test.foo', new ResourceLoaderTestModule() );
123 $resourceLoader->register( 'test.bar', new ResourceLoaderTestModule() );
125 [ 'test.foo', 'test.bar' ],
126 $resourceLoader->getModuleNames()
130 public function provideTestIsFileModule() {
131 $fileModuleObj = $this->getMockBuilder( ResourceLoaderFileModule
::class )
132 ->disableOriginalConstructor()
136 new ResourceLoaderTestModule()
138 'FileModule object' => [ false,
141 'simple empty' => [ true,
144 'simple scripts' => [ true,
145 [ 'scripts' => 'example.js' ]
147 'simple scripts, raw and targets' => [ true, [
148 'scripts' => [ 'a.js', 'b.js' ],
150 'targets' => [ 'desktop', 'mobile' ],
152 'FileModule' => [ true,
153 [ 'class' => ResourceLoaderFileModule
::class, 'scripts' => 'example.js' ]
155 'TestModule' => [ false,
156 [ 'class' => ResourceLoaderTestModule
::class, 'scripts' => 'example.js' ]
158 'SkinModule (FileModule subclass)' => [ true,
159 [ 'class' => ResourceLoaderSkinModule
::class, 'scripts' => 'example.js' ]
161 'JqueryMsgModule (FileModule subclass)' => [ true, [
162 'class' => ResourceLoaderJqueryMsgModule
::class,
163 'scripts' => 'example.js',
165 'WikiModule' => [ false, [
166 'class' => ResourceLoaderWikiModule
::class,
167 'scripts' => [ 'MediaWiki:Example.js' ],
173 * @dataProvider provideTestIsFileModule
174 * @covers ResourceLoader::isFileModule
176 public function testIsFileModule( $expected, $module ) {
177 $rl = TestingAccessWrapper
::newFromObject( new EmptyResourceLoader() );
178 $rl->register( 'test', $module );
179 $this->assertSame( $expected, $rl->isFileModule( 'test' ) );
183 * @covers ResourceLoader::isFileModule
185 public function testIsFileModuleUnknown() {
186 $rl = TestingAccessWrapper
::newFromObject( new EmptyResourceLoader() );
187 $this->assertSame( false, $rl->isFileModule( 'unknown' ) );
191 * @covers ResourceLoader::isModuleRegistered
193 public function testIsModuleRegistered() {
194 $rl = new EmptyResourceLoader();
195 $rl->register( 'test', new ResourceLoaderTestModule() );
196 $this->assertTrue( $rl->isModuleRegistered( 'test' ) );
197 $this->assertFalse( $rl->isModuleRegistered( 'test.unknown' ) );
201 * @covers ResourceLoader::getModule
203 public function testGetModuleUnknown() {
204 $rl = new EmptyResourceLoader();
205 $this->assertSame( null, $rl->getModule( 'test' ) );
209 * @covers ResourceLoader::getModule
211 public function testGetModuleClass() {
212 $rl = new EmptyResourceLoader();
213 $rl->register( 'test', [ 'class' => ResourceLoaderTestModule
::class ] );
214 $this->assertInstanceOf(
215 ResourceLoaderTestModule
::class,
216 $rl->getModule( 'test' )
221 * @covers ResourceLoader::getModule
223 public function testGetModuleFactory() {
224 $factory = function ( array $info ) {
225 $this->assertArrayHasKey( 'kitten', $info );
226 return new ResourceLoaderTestModule( $info );
229 $rl = new EmptyResourceLoader();
230 $rl->register( 'test', [ 'factory' => $factory, 'kitten' => 'little ball of fur' ] );
231 $this->assertInstanceOf(
232 ResourceLoaderTestModule
::class,
233 $rl->getModule( 'test' )
238 * @covers ResourceLoader::getModule
240 public function testGetModuleClassDefault() {
241 $rl = new EmptyResourceLoader();
242 $rl->register( 'test', [] );
243 $this->assertInstanceOf(
244 ResourceLoaderFileModule
::class,
245 $rl->getModule( 'test' ),
246 'Array-style module registrations default to FileModule'
251 * @covers ResourceLoaderFileModule::compileLessFile
253 public function testLessFileCompilation() {
254 $context = $this->getResourceLoaderContext();
255 $basePath = __DIR__
. '/../../data/less/module';
256 $module = new ResourceLoaderFileModule( [
257 'localBasePath' => $basePath,
258 'styles' => [ 'styles.less' ],
260 $module->setName( 'test.less' );
261 $styles = $module->getStyles( $context );
262 $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
266 * @covers ResourceLoader::getLessCompiler
268 public function testLessImportDirs() {
269 $rl = new EmptyResourceLoader();
270 $lc = $rl->getLessCompiler( $rl->getLessVars() );
271 $basePath = dirname( dirname( __DIR__
) ) . '/data/less';
272 $lc->SetImportDirs( [
273 "$basePath/common" => '',
275 $css = $lc->parseFile( "$basePath/module/use-import-dir.less" )->getCss();
276 $this->assertStringEqualsFile( "$basePath/module/styles.css", $css );
279 public static function providePackedModules() {
282 'Example from makePackedModulesString doc comment',
283 [ 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' ],
284 'foo.bar,baz|bar.baz,quux',
287 'Example from expandModuleNames doc comment',
288 [ 'jquery.foo', 'jquery.bar', 'jquery.ui.baz', 'jquery.ui.quux' ],
289 'jquery.foo,bar|jquery.ui.baz,quux',
292 'Regression fixed in r87497 (7fee86c38e) with dotless names',
293 [ 'foo', 'bar', 'baz' ],
297 'Prefixless modules after a prefixed module',
298 [ 'single.module', 'foobar', 'foobaz' ],
299 'single.module|foobar,foobaz',
303 [ 'foo', 'foo.baz', 'baz.quux', 'foo.bar' ],
304 'foo|foo.baz,bar|baz.quux',
305 [ 'foo', 'foo.baz', 'foo.bar', 'baz.quux' ],
311 * @dataProvider providePackedModules
312 * @covers ResourceLoader::makePackedModulesString
314 public function testMakePackedModulesString( $desc, $modules, $packed ) {
315 $this->assertEquals( $packed, ResourceLoader
::makePackedModulesString( $modules ), $desc );
319 * @dataProvider providePackedModules
320 * @covers ResourceLoaderContext::expandModuleNames
322 public function testExpandModuleNames( $desc, $modules, $packed, $unpacked = null ) {
324 $unpacked ?
: $modules,
325 ResourceLoaderContext
::expandModuleNames( $packed ),
330 public static function provideAddSource() {
332 [ 'foowiki', 'https://example.org/w/load.php', 'foowiki' ],
333 [ 'foowiki', [ 'loadScript' => 'https://example.org/w/load.php' ], 'foowiki' ],
336 'foowiki' => 'https://example.org/w/load.php',
337 'bazwiki' => 'https://example.com/w/load.php',
340 [ 'foowiki', 'bazwiki' ]
346 * @dataProvider provideAddSource
347 * @covers ResourceLoader::addSource
348 * @covers ResourceLoader::getSources
350 public function testAddSource( $name, $info, $expected ) {
351 $rl = new ResourceLoader
;
352 $rl->addSource( $name, $info );
353 if ( is_array( $expected ) ) {
354 foreach ( $expected as $source ) {
355 $this->assertArrayHasKey( $source, $rl->getSources() );
358 $this->assertArrayHasKey( $expected, $rl->getSources() );
363 * @covers ResourceLoader::addSource
365 public function testAddSourceDupe() {
366 $rl = new ResourceLoader
;
367 $this->setExpectedException(
368 MWException
::class, 'ResourceLoader duplicate source addition error'
370 $rl->addSource( 'foo', 'https://example.org/w/load.php' );
371 $rl->addSource( 'foo', 'https://example.com/w/load.php' );
375 * @covers ResourceLoader::addSource
377 public function testAddSourceInvalid() {
378 $rl = new ResourceLoader
;
379 $this->setExpectedException( MWException
::class, 'with no "loadScript" key' );
380 $rl->addSource( 'foo', [ 'x' => 'https://example.org/w/load.php' ] );
383 public static function provideLoaderImplement() {
386 'title' => 'Implement scripts, styles and messages',
388 'name' => 'test.example',
389 'scripts' => 'mw.example();',
390 'styles' => [ 'css' => [ '.mw-example {}' ] ],
391 'messages' => [ 'example' => '' ],
394 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
405 'title' => 'Implement scripts',
407 'name' => 'test.example',
408 'scripts' => 'mw.example();',
411 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
416 'title' => 'Implement styles',
418 'name' => 'test.example',
420 'styles' => [ 'css' => [ '.mw-example {}' ] ],
422 'expected' => 'mw.loader.implement( "test.example", [], {
429 'title' => 'Implement scripts and messages',
431 'name' => 'test.example',
432 'scripts' => 'mw.example();',
433 'messages' => [ 'example' => '' ],
435 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
442 'title' => 'Implement scripts and templates',
444 'name' => 'test.example',
445 'scripts' => 'mw.example();',
446 'templates' => [ 'example.html' => '' ],
448 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
455 'title' => 'Implement unwrapped user script',
458 'scripts' => 'mw.example( 1 );',
461 'expected' => 'mw.loader.implement( "user", "mw.example( 1 );" );',
467 * @dataProvider provideLoaderImplement
468 * @covers ResourceLoader::makeLoaderImplementScript
469 * @covers ResourceLoader::trimArray
471 public function testMakeLoaderImplementScript( $case ) {
474 'styles' => [], 'templates' => [], 'messages' => new XmlJsCode( '{}' )
476 ResourceLoader
::clearCache();
477 $this->setMwGlobals( 'wgResourceLoaderDebug', true );
479 $rl = TestingAccessWrapper
::newFromClass( ResourceLoader
::class );
482 $rl->makeLoaderImplementScript(
484 ( $case['wrap'] && is_string( $case['scripts'] ) )
485 ?
new XmlJsCode( $case['scripts'] )
495 * @covers ResourceLoader::makeLoaderImplementScript
497 public function testMakeLoaderImplementScriptInvalid() {
498 $this->setExpectedException( MWException
::class, 'Invalid scripts error' );
499 $rl = TestingAccessWrapper
::newFromClass( ResourceLoader
::class );
500 $rl->makeLoaderImplementScript(
510 * @covers ResourceLoader::makeLoaderRegisterScript
512 public function testMakeLoaderRegisterScript() {
514 'mw.loader.register( [
520 ResourceLoader
::makeLoaderRegisterScript( [
521 [ 'test.name', '1234567' ],
523 'Nested array parameter'
527 'mw.loader.register( "test.name", "1234567" );',
528 ResourceLoader
::makeLoaderRegisterScript(
532 'Variadic parameters'
537 * @covers ResourceLoader::makeLoaderSourcesScript
539 public function testMakeLoaderSourcesScript() {
541 'mw.loader.addSource( "local", "/w/load.php" );',
542 ResourceLoader
::makeLoaderSourcesScript( 'local', '/w/load.php' )
545 'mw.loader.addSource( {
546 "local": "/w/load.php"
548 ResourceLoader
::makeLoaderSourcesScript( [ 'local' => '/w/load.php' ] )
551 'mw.loader.addSource( {
552 "local": "/w/load.php",
553 "example": "https://example.org/w/load.php"
555 ResourceLoader
::makeLoaderSourcesScript( [
556 'local' => '/w/load.php',
557 'example' => 'https://example.org/w/load.php'
561 'mw.loader.addSource( [] );',
562 ResourceLoader
::makeLoaderSourcesScript( [] )
566 private static function fakeSources() {
569 'loadScript' => '//example.org/w/load.php',
570 'apiScript' => '//example.org/w/api.php',
573 'loadScript' => '//example.com/w/load.php',
574 'apiScript' => '//example.com/w/api.php',
580 * @covers ResourceLoader::getLoadScript
582 public function testGetLoadScript() {
583 $this->setMwGlobals( 'wgResourceLoaderSources', [] );
584 $rl = new ResourceLoader();
585 $sources = self
::fakeSources();
586 $rl->addSource( $sources );
587 foreach ( [ 'examplewiki', 'example2wiki' ] as $name ) {
588 $this->assertEquals( $rl->getLoadScript( $name ), $sources[$name]['loadScript'] );
592 $rl->getLoadScript( 'thiswasneverreigstered' );
593 $this->assertTrue( false, 'ResourceLoader::getLoadScript should have thrown an exception' );
594 } catch ( MWException
$e ) {
595 $this->assertTrue( true );
599 protected function getFailFerryMock( $getter = 'getScript' ) {
600 $mock = $this->getMockBuilder( ResourceLoaderTestModule
::class )
601 ->setMethods( [ $getter ] )
603 $mock->method( $getter )->will( $this->throwException(
604 new Exception( 'Ferry not found' )
609 protected function getSimpleModuleMock( $script = '' ) {
610 $mock = $this->getMockBuilder( ResourceLoaderTestModule
::class )
611 ->setMethods( [ 'getScript' ] )
613 $mock->method( 'getScript' )->willReturn( $script );
617 protected function getSimpleStyleModuleMock( $styles = '' ) {
618 $mock = $this->getMockBuilder( ResourceLoaderTestModule
::class )
619 ->setMethods( [ 'getStyles' ] )
621 $mock->method( 'getStyles' )->willReturn( [ '' => $styles ] );
626 * @covers ResourceLoader::getCombinedVersion
628 public function testGetCombinedVersion() {
629 $rl = $this->getMockBuilder( EmptyResourceLoader
::class )
630 // Disable log from outputErrorAndLog
631 ->setMethods( [ 'outputErrorAndLog' ] )->getMock();
633 'foo' => self
::getSimpleModuleMock(),
634 'ferry' => self
::getFailFerryMock(),
635 'bar' => self
::getSimpleModuleMock(),
637 $context = $this->getResourceLoaderContext( [], $rl );
641 $rl->getCombinedVersion( $context, [] ),
646 ResourceLoader
::makeHash( self
::BLANK_VERSION
),
647 $rl->getCombinedVersion( $context, [ 'foo' ] ),
651 // Verify that getCombinedVersion() does not throw when ferry fails.
652 // Instead it gracefully continues to combine the remaining modules.
654 ResourceLoader
::makeHash( self
::BLANK_VERSION
. self
::BLANK_VERSION
),
655 $rl->getCombinedVersion( $context, [ 'foo', 'ferry', 'bar' ] ),
656 'compute foo+ferry+bar (T152266)'
660 public static function provideMakeModuleResponseConcat() {
666 'expected' => "foo()\n" . 'mw.loader.state( {
669 'minified' => "foo()\n" . 'mw.loader.state({"foo":"ready"});',
670 'message' => 'Script without semi-colon',
677 'expected' => "foo()\nbar()\n" . 'mw.loader.state( {
681 'minified' => "foo()\nbar()\n" . 'mw.loader.state({"foo":"ready","bar":"ready"});',
682 'message' => 'Two scripts without semi-colon',
686 'foo' => "foo()\n// bar();"
688 'expected' => "foo()\n// bar();\n" . 'mw.loader.state( {
691 'minified' => "foo()\n" . 'mw.loader.state({"foo":"ready"});',
692 'message' => 'Script with semi-colon in comment (T162719)',
696 foreach ( $testcases as $i => $case ) {
703 $ret["#$i (minified)"] = [
714 * Verify how multiple scripts and mw.loader.state() calls are concatenated.
716 * @dataProvider provideMakeModuleResponseConcat
717 * @covers ResourceLoader::makeModuleResponse
719 public function testMakeModuleResponseConcat( $scripts, $expected, $debug, $message = null ) {
720 $rl = new EmptyResourceLoader();
721 $modules = array_map( function ( $script ) {
722 return self
::getSimpleModuleMock( $script );
724 $rl->register( $modules );
726 $this->setMwGlobals( 'wgResourceLoaderDebug', $debug );
727 $context = $this->getResourceLoaderContext(
729 'modules' => implode( '|', array_keys( $modules ) ),
735 $response = $rl->makeModuleResponse( $context, $modules );
736 $this->assertSame( [], $rl->getErrors(), 'Errors' );
737 $this->assertEquals( $expected, $response, $message ?
: 'Response' );
741 * Verify that when building module content in a load.php response,
742 * an exception from one module will not break script output from
745 * @covers ResourceLoader::makeModuleResponse
747 public function testMakeModuleResponseError() {
749 'foo' => self
::getSimpleModuleMock( 'foo();' ),
750 'ferry' => self
::getFailFerryMock(),
751 'bar' => self
::getSimpleModuleMock( 'bar();' ),
753 $rl = new EmptyResourceLoader();
754 $rl->register( $modules );
755 $context = $this->getResourceLoaderContext(
757 'modules' => 'foo|ferry|bar',
763 // Disable log from makeModuleResponse via outputErrorAndLog
764 $this->setLogger( 'exception', new Psr\Log\
NullLogger() );
766 $response = $rl->makeModuleResponse( $context, $modules );
767 $errors = $rl->getErrors();
769 $this->assertCount( 1, $errors );
770 $this->assertRegExp( '/Ferry not found/', $errors[0] );
772 "foo();\nbar();\n" . 'mw.loader.state( {
782 * Verify that exceptions in PHP for one module will not break others
783 * (stylesheet response).
785 * @covers ResourceLoader::makeModuleResponse
787 public function testMakeModuleResponseErrorCSS() {
789 'foo' => self
::getSimpleStyleModuleMock( '.foo{}' ),
790 'ferry' => self
::getFailFerryMock( 'getStyles' ),
791 'bar' => self
::getSimpleStyleModuleMock( '.bar{}' ),
793 $rl = new EmptyResourceLoader();
794 $rl->register( $modules );
795 $context = $this->getResourceLoaderContext(
797 'modules' => 'foo|ferry|bar',
804 // Disable log from makeModuleResponse via outputErrorAndLog
805 $this->setLogger( 'exception', new Psr\Log\
NullLogger() );
807 $response = $rl->makeModuleResponse( $context, $modules );
808 $errors = $rl->getErrors();
810 $this->assertCount( 2, $errors );
811 $this->assertRegExp( '/Ferry not found/', $errors[0] );
812 $this->assertRegExp( '/Problem.+\n\s*"ferry":\s*"error"/m', $errors[1] );
819 * Verify that when building the startup module response,
820 * an exception from one module class will not break the entire
821 * startup module response. See T152266.
823 * @covers ResourceLoader::makeModuleResponse
825 public function testMakeModuleResponseStartupError() {
826 $rl = new EmptyResourceLoader();
828 'foo' => self
::getSimpleModuleMock( 'foo();' ),
829 'ferry' => self
::getFailFerryMock(),
830 'bar' => self
::getSimpleModuleMock( 'bar();' ),
831 'startup' => [ 'class' => ResourceLoaderStartUpModule
::class ],
833 $context = $this->getResourceLoaderContext(
835 'modules' => 'startup',
842 [ 'foo', 'ferry', 'bar', 'startup' ],
843 $rl->getModuleNames(),
847 // Disable log from makeModuleResponse via outputErrorAndLog
848 $this->setLogger( 'exception', new Psr\Log\
NullLogger() );
850 $modules = [ 'startup' => $rl->getModule( 'startup' ) ];
851 $response = $rl->makeModuleResponse( $context, $modules );
852 $errors = $rl->getErrors();
854 $this->assertRegExp( '/Ferry not found/', $errors[0] );
855 $this->assertCount( 1, $errors );
857 '/isCompatible.*function startUp/s',
859 'startup response undisrupted (T152266)'
862 '/register\([^)]+"ferry",\s*""/s',
864 'startup response registers broken module'
867 '/state\([^)]+"ferry":\s*"error"/s',
869 'startup response sets state to error'
874 * Integration test for modules sending extra HTTP response headers.
876 * @covers ResourceLoaderModule::getHeaders
877 * @covers ResourceLoaderModule::buildContent
878 * @covers ResourceLoader::makeModuleResponse
880 public function testMakeModuleResponseExtraHeaders() {
881 $module = $this->getMockBuilder( ResourceLoaderTestModule
::class )
882 ->setMethods( [ 'getPreloadLinks' ] )->getMock();
883 $module->method( 'getPreloadLinks' )->willReturn( [
884 'https://example.org/script.js' => [ 'as' => 'script' ],
887 $rl = new EmptyResourceLoader();
891 $context = $this->getResourceLoaderContext(
892 [ 'modules' => 'foo', 'only' => 'scripts' ],
896 $modules = [ 'foo' => $rl->getModule( 'foo' ) ];
897 $response = $rl->makeModuleResponse( $context, $modules );
898 $extraHeaders = TestingAccessWrapper
::newFromObject( $rl )->extraHeaders
;
902 'Link: <https://example.org/script.js>;rel=preload;as=script'
910 * @covers ResourceLoaderModule::getHeaders
911 * @covers ResourceLoaderModule::buildContent
912 * @covers ResourceLoader::makeModuleResponse
914 public function testMakeModuleResponseExtraHeadersMulti() {
915 $foo = $this->getMockBuilder( ResourceLoaderTestModule
::class )
916 ->setMethods( [ 'getPreloadLinks' ] )->getMock();
917 $foo->method( 'getPreloadLinks' )->willReturn( [
918 'https://example.org/script.js' => [ 'as' => 'script' ],
921 $bar = $this->getMockBuilder( ResourceLoaderTestModule
::class )
922 ->setMethods( [ 'getPreloadLinks' ] )->getMock();
923 $bar->method( 'getPreloadLinks' )->willReturn( [
924 '/example.png' => [ 'as' => 'image' ],
925 '/example.jpg' => [ 'as' => 'image' ],
928 $rl = new EmptyResourceLoader();
929 $rl->register( [ 'foo' => $foo, 'bar' => $bar ] );
930 $context = $this->getResourceLoaderContext(
931 [ 'modules' => 'foo|bar', 'only' => 'scripts' ],
935 $modules = [ 'foo' => $rl->getModule( 'foo' ), 'bar' => $rl->getModule( 'bar' ) ];
936 $response = $rl->makeModuleResponse( $context, $modules );
937 $extraHeaders = TestingAccessWrapper
::newFromObject( $rl )->extraHeaders
;
940 'Link: <https://example.org/script.js>;rel=preload;as=script',
941 'Link: </example.png>;rel=preload;as=image,</example.jpg>;rel=preload;as=image'
949 * @covers ResourceLoader::respond
951 public function testRespond() {
952 $rl = $this->getMockBuilder( EmptyResourceLoader
::class )
954 'tryRespondNotModified',
955 'sendResponseHeaders',
956 'measureResponseTime',
959 $context = $this->getResourceLoaderContext( [ 'modules' => '' ], $rl );
961 $rl->expects( $this->once() )->method( 'measureResponseTime' );
962 $this->expectOutputRegex( '/no modules were requested/' );
964 $rl->respond( $context );
968 * @covers ResourceLoader::measureResponseTime
970 public function testMeasureResponseTime() {
971 $stats = $this->getMockBuilder( NullStatsdDataFactory
::class )
972 ->setMethods( [ 'timing' ] )->getMock();
973 $this->setService( 'StatsdDataFactory', $stats );
975 $stats->expects( $this->once() )->method( 'timing' )
976 ->with( 'resourceloader.responseTime', $this->anything() );
978 $timing = new Timing();
979 $timing->mark( 'requestShutdown' );
980 $rl = TestingAccessWrapper
::newFromObject( new EmptyResourceLoader
);
981 $rl->measureResponseTime( $timing );
982 DeferredUpdates
::doUpdates();