'Foo' => '#eeeeee',
'bar' => 5,
],
+ // Clear ResourceLoaderGetConfigVars hooks (called by StartupModule)
+ // to avoid notices during testMakeModuleResponse for missing
+ // wgResourceLoaderLESSVars keys in extension hooks.
+ 'wgHooks' => [],
] );
}
$this->assertEquals( $module, $resourceLoader->getModule( 'test' ) );
}
+ /**
+ * @covers ResourceLoader::register
+ */
+ public function testRegisterEmptyString() {
+ $module = new ResourceLoaderTestModule();
+ $resourceLoader = new EmptyResourceLoader();
+ $resourceLoader->register( '', $module );
+ $this->assertEquals( $module, $resourceLoader->getModule( '' ) );
+ }
+
/**
* @covers ResourceLoader::register
*/
'name' => 'test.example',
'scripts' => [],
'styles' => [ 'css' => [ '.mw-example {}' ] ],
- 'messages' => new XmlJsCode( '{}' ),
'expected' => 'mw.loader.implement( "test.example", [], {
"css": [
'name' => 'user',
'scripts' => 'mw.example( 1 );',
+ 'wrap' => false,
'expected' => 'mw.loader.implement( "user", "mw.example( 1 );" );',
] ],
- [ [
- 'title' => 'Implement unwrapped user script',
- 'debug' => false,
-
- 'name' => 'user',
- 'scripts' => 'mw.example( 1 );',
-
- 'expected' => 'mw.loader.implement("user","mw.example(1);");',
- ] ],
];
}
*/
public function testMakeLoaderImplementScript( $case ) {
$case += [
- 'styles' => [], 'templates' => [], 'messages' => new XmlJsCode( '{}' ),
- 'debug' => true
+ 'wrap' => true,
+ 'styles' => [], 'templates' => [], 'messages' => new XmlJsCode( '{}' )
];
ResourceLoader::clearCache();
- $this->setMwGlobals( 'wgResourceLoaderDebug', $case['debug'] );
+ $this->setMwGlobals( 'wgResourceLoaderDebug', true );
+ $rl = TestingAccessWrapper::newFromClass( 'ResourceLoader' );
$this->assertEquals(
$case['expected'],
- ResourceLoader::makeLoaderImplementScript(
+ $rl->makeLoaderImplementScript(
$case['name'],
- $case['scripts'],
+ ( $case['wrap'] && is_string( $case['scripts'] ) )
+ ? new XmlJsCode( $case['scripts'] )
+ : $case['scripts'],
$case['styles'],
$case['messages'],
$case['templates']
*/
public function testMakeLoaderImplementScriptInvalid() {
$this->setExpectedException( 'MWException', 'Invalid scripts error' );
- ResourceLoader::makeLoaderImplementScript(
+ $rl = TestingAccessWrapper::newFromClass( 'ResourceLoader' );
+ $rl->makeLoaderImplementScript(
'test', // name
123, // scripts
null, // styles
$this->assertTrue( true );
}
}
+
+ protected function getFailFerryMock() {
+ $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
+ ->setMethods( [ 'getScript' ] )
+ ->getMock();
+ $mock->method( 'getScript' )->will( $this->throwException(
+ new Exception( 'Ferry not found' )
+ ) );
+ return $mock;
+ }
+
+ protected function getSimpleModuleMock( $script = '' ) {
+ $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
+ ->setMethods( [ 'getScript' ] )
+ ->getMock();
+ $mock->method( 'getScript' )->willReturn( $script );
+ return $mock;
+ }
+
+ /**
+ * @covers ResourceLoader::getCombinedVersion
+ */
+ public function testGetCombinedVersion() {
+ $rl = new EmptyResourceLoader();
+ $rl->register( [
+ 'foo' => self::getSimpleModuleMock(),
+ 'ferry' => self::getFailFerryMock(),
+ 'bar' => self::getSimpleModuleMock(),
+ ] );
+ $context = $this->getResourceLoaderContext( [], $rl );
+
+ $this->assertEquals(
+ ResourceLoader::makeHash( self::BLANK_VERSION ),
+ $rl->getCombinedVersion( $context, [ 'foo' ] ),
+ 'compute foo'
+ );
+
+ // Verify that getCombinedVersion() does not throw when ferry fails.
+ // Instead it gracefully continues to combine the remaining modules.
+ $this->assertEquals(
+ ResourceLoader::makeHash( self::BLANK_VERSION . self::BLANK_VERSION ),
+ $rl->getCombinedVersion( $context, [ 'foo', 'ferry', 'bar' ] ),
+ 'compute foo+ferry+bar (T152266)'
+ );
+ }
+
+ /**
+ * Verify that when building module content in a load.php response,
+ * an exception from one module will not break script output from
+ * other modules.
+ */
+ public function testMakeModuleResponseError() {
+ $modules = [
+ 'foo' => self::getSimpleModuleMock( 'foo();' ),
+ 'ferry' => self::getFailFerryMock(),
+ 'bar' => self::getSimpleModuleMock( 'bar();' ),
+ ];
+ $rl = new EmptyResourceLoader();
+ $rl->register( $modules );
+ $context = $this->getResourceLoaderContext(
+ [
+ 'modules' => 'foo|ferry|bar',
+ 'only' => 'scripts',
+ ],
+ $rl
+ );
+
+ $response = $rl->makeModuleResponse( $context, $modules );
+ $errors = $rl->getErrors();
+
+ $this->assertCount( 1, $errors );
+ $this->assertRegExp( '/Ferry not found/', $errors[0] );
+ $this->assertEquals(
+ 'foo();bar();mw.loader.state( {
+ "ferry": "error",
+ "foo": "ready",
+ "bar": "ready"
+} );',
+ $response
+ );
+ }
+
+ /**
+ * Verify that when building the startup module response,
+ * an exception from one module class will not break the entire
+ * startup module response. See T152266.
+ */
+ public function testMakeModuleResponseStartupError() {
+ $rl = new EmptyResourceLoader();
+ $rl->register( [
+ 'foo' => self::getSimpleModuleMock( 'foo();' ),
+ 'ferry' => self::getFailFerryMock(),
+ 'bar' => self::getSimpleModuleMock( 'bar();' ),
+ 'startup' => [ 'class' => 'ResourceLoaderStartUpModule' ],
+ ] );
+ $context = $this->getResourceLoaderContext(
+ [
+ 'modules' => 'startup',
+ 'only' => 'scripts',
+ ],
+ $rl
+ );
+
+ $this->assertEquals(
+ [ 'foo', 'ferry', 'bar', 'startup' ],
+ $rl->getModuleNames(),
+ 'getModuleNames'
+ );
+
+ $modules = [ 'startup' => $rl->getModule( 'startup' ) ];
+ $response = $rl->makeModuleResponse( $context, $modules );
+ $errors = $rl->getErrors();
+
+ $this->assertRegExp( '/Ferry not found/', $errors[0] );
+ $this->assertCount( 1, $errors );
+ $this->assertRegExp(
+ '/isCompatible.*function startUp/s',
+ $response,
+ 'startup response undisrupted (T152266)'
+ );
+ $this->assertRegExp(
+ '/register\([^)]+"ferry",\s*""/s',
+ $response,
+ 'startup response registers broken module'
+ );
+ $this->assertRegExp(
+ '/state\([^)]+"ferry":\s*"error"/s',
+ $response,
+ 'startup response sets state to error'
+ );
+ }
}