3 namespace MediaWiki\Tests\ResourceLoader
;
6 use MediaWiki\MainConfigNames
;
7 use MediaWiki\ResourceLoader\FileModule
;
8 use MediaWiki\ResourceLoader\MessageBlobStore
;
9 use MediaWiki\ResourceLoader\Module
;
10 use MediaWiki\ResourceLoader\ResourceLoader
;
14 * @group ResourceLoader
15 * @covers \MediaWiki\ResourceLoader\Module
17 class ModuleTest
extends ResourceLoaderTestCase
{
19 public function testGetVersionHash() {
20 $context = $this->getResourceLoaderContext( [ 'debug' => 'false' ] );
21 $msgBlobStore = $this->createMock( MessageBlobStore
::class );
22 $msgBlobStore->method( 'getBlob' )->willReturn( '{}' );
23 $context->getResourceLoader()->setMessageBlobStore( $msgBlobStore );
26 'scripts' => [ 'foo.js', 'bar.js' ],
27 'dependencies' => [ 'jquery', 'mediawiki' ],
28 'messages' => [ 'hello', 'world' ],
31 $module = new FileModule( $baseParams );
32 $module->setName( 'test' );
33 $version = json_encode( $module->getVersionHash( $context ) );
36 $module = new FileModule( $baseParams );
37 $module->setName( 'test' );
40 json_encode( $module->getVersionHash( $context ) ),
41 'Instance is insignificant'
44 // Re-order dependencies
45 $module = new FileModule( [
46 'dependencies' => [ 'mediawiki', 'jquery' ],
48 $module->setName( 'test' );
51 json_encode( $module->getVersionHash( $context ) ),
52 'Order of dependencies is insignificant'
56 $module = new FileModule( [
57 'messages' => [ 'world', 'hello' ],
59 $module->setName( 'test' );
62 json_encode( $module->getVersionHash( $context ) ),
63 'Order of messages is insignificant'
67 $module = new FileModule( [
68 'scripts' => [ 'bar.js', 'foo.js' ],
70 $module->setName( 'test' );
71 $this->assertNotEquals(
73 json_encode( $module->getVersionHash( $context ) ),
74 'Order of scripts is significant'
78 $module = new ResourceLoaderFileModuleTestingSubclass( $baseParams );
79 $module->setName( 'test' );
80 $this->assertNotEquals(
82 json_encode( $module->getVersionHash( $context ) ),
83 'Class is significant'
87 public function testGetVersionHash_debug() {
88 $module = new ResourceLoaderTestModule( [ 'script' => 'foo();' ] );
89 $module->setName( 'test' );
90 $context = $this->getResourceLoaderContext( [ 'debug' => 'true' ] );
91 $this->assertSame( '', $module->getVersionHash( $context ) );
94 public function testGetVersionHash_length() {
95 $context = $this->getResourceLoaderContext( [ 'debug' => 'false' ] );
96 $module = new ResourceLoaderTestModule( [
99 $module->setName( 'test' );
100 $version = $module->getVersionHash( $context );
101 $this->assertSame( ResourceLoader
::HASH_LENGTH
, strlen( $version ), 'Hash length' );
104 public function testGetVersionHash_parentDefinition() {
105 $context = $this->getResourceLoaderContext( [ 'debug' => 'false' ] );
106 $module = $this->getMockBuilder( Module
::class )
107 ->onlyMethods( [ 'getDefinitionSummary' ] )->getMock();
108 $module->method( 'getDefinitionSummary' )->willReturn( [ 'a' => 'summary' ] );
109 $module->setName( 'test' );
111 $this->expectException( LogicException
::class );
112 $this->expectExceptionMessage( 'must call parent' );
113 $module->getVersionHash( $context );
117 * @covers \MediaWiki\ResourceLoader\Module
118 * @covers \MediaWiki\ResourceLoader\ResourceLoader
120 public function testGetURLsForDebug() {
121 $module = new ResourceLoaderTestModule( [
122 'script' => 'foo();',
123 'styles' => '.foo { color: blue; }',
125 $context = $this->getResourceLoaderContext( [ 'debug' => 'true' ] );
126 $module->setConfig( $context->getResourceLoader()->getConfig() );
127 $module->setName( 'test' );
131 'https://example.org/w/load.php?debug=1&lang=en&modules=test&only=scripts'
133 $module->getScriptURLsForDebug( $context ),
134 'script urls debug=true'
138 '/w/load.php?debug=1&lang=en&modules=test&only=styles'
140 $module->getStyleURLsForDebug( $context ),
141 'style urls debug=true'
144 $context = $this->getResourceLoaderContext( [ 'debug' => '2' ] );
147 'https://example.org/w/load.php?debug=2&lang=en&modules=test&only=scripts'
149 $module->getScriptURLsForDebug( $context ),
150 'script urls debug=2'
154 '/w/load.php?debug=2&lang=en&modules=test&only=styles'
156 $module->getStyleURLsForDebug( $context ),
161 public static function provideValidateScripts() {
162 yield
'valid ES5' => [ "\n'valid';" ];
164 yield
'valid ES6/ES2015 for-of' => [
165 "var x = ['a', 'b']; for (var key of x) { console.log(key); }"
168 yield
'valid ES2016 exponentiation' => [
169 "var x = 2; var y = 3; console.log(x ** y);"
172 yield
'valid ES2017 async-await' => [
173 "var foo = async function(x) { return await x.fetch(); }",
174 'Parse error: Unexpected: function on line 1'
177 yield
'valid ES2018 spread in object literal' => [
178 "var x = {b: 2, c: 3}; var y = {a: 1, ...x};",
179 'Parse error: Unexpected: ... on line 1'
182 yield
'SyntaxError' => [
183 "var a = 'this is';\n {\ninvalid",
184 'Parse error: Unclosed { on line 3'
187 // If an implementation matches inputs using a regex with runaway backtracking,
188 // then inputs with more than ~3072 repetitions are likely to fail (T299537).
189 $input = '"' . str_repeat( 'x', 10000 ) . '";';
190 yield
'double quote string 10K' => [ $input, ];
191 $input = '\'' . str_repeat( 'x', 10000 ) . '\';';
192 yield
'single quote string 10K' => [ $input ];
193 $input = '"' . str_repeat( '\u0021', 100 ) . '";';
194 yield
'escaping string 100' => [ $input ];
195 $input = '"' . str_repeat( '\u0021', 10000 ) . '";';
196 yield
'escaping string 10K' => [ $input ];
197 $input = '/' . str_repeat( 'x', 1000 ) . '/;';
198 yield
'regex 1K' => [ $input ];
199 $input = '/' . str_repeat( 'x', 10000 ) . '/;';
200 yield
'regex 10K' => [ $input ];
201 $input = '/' . str_repeat( '\u0021', 100 ) . '/;';
202 yield
'escaping regex 100' => [ $input ];
203 $input = '/' . str_repeat( '\u0021', 10000 ) . '/;';
204 yield
'escaping regex 10K' => [ $input ];
208 * @dataProvider provideValidateScripts
210 public function testValidateScriptFile( $input, $error = null ) {
211 $this->overrideConfigValue( MainConfigNames
::ResourceLoaderValidateJS
, true );
213 $context = $this->getResourceLoaderContext();
215 $module = new ResourceLoaderTestModule( [
216 'mayValidateScript' => true,
219 $module->setConfig( $context->getResourceLoader()->getConfig() );
221 $result = $module->getScript( $context );
223 $this->assertStringContainsString( 'mw.log.error(', $result, 'log error' );
224 $this->assertStringContainsString( $error, $result, 'error message' );
228 $module->getScript( $context ),
229 'Leave valid scripts as-is'
234 public static function provideBuildContentScripts() {
249 "mw.foo()\n// mw.bar();",
252 "mw.foo()\n// mw.bar()",
255 "mw.foo()// mw.bar();",
261 * @dataProvider provideBuildContentScripts
263 public function testBuildContentScripts( $raw, $message = '' ) {
264 $context = $this->getResourceLoaderContext();
265 $module = new ResourceLoaderTestModule( [
268 $module->setName( 'test' );
269 $this->assertEquals( $raw, $module->getScript( $context ), 'Raw script' );
271 [ 'plainScripts' => [ [ 'content' => $raw ] ] ],
272 $module->getModuleContent( $context )[ 'scripts' ],
277 public function testPlaceholderize() {
278 $getRelativePaths = new ReflectionMethod( Module
::class, 'getRelativePaths' );
279 $getRelativePaths->setAccessible( true );
280 $expandRelativePaths = new ReflectionMethod( Module
::class, 'expandRelativePaths' );
281 $expandRelativePaths->setAccessible( true );
283 $this->setMwGlobals( [
284 'IP' => '/srv/example/mediawiki/core',
287 '/srv/example/mediawiki/core/resources/foo.js',
288 '/srv/example/mediawiki/core/extensions/Example/modules/bar.js',
289 '/srv/example/mediawiki/skins/Example/baz.css',
290 '/srv/example/mediawiki/skins/Example/images/quux.png',
294 'extensions/Example/modules/bar.js',
295 '../skins/Example/baz.css',
296 '../skins/Example/images/quux.png',
300 $getRelativePaths->invoke( null, $raw ),
301 'Insert placeholders'
305 $expandRelativePaths->invoke( null, $canonical ),
306 'Substitute placeholders'
310 public function testGetHeaders() {
311 $context = $this->getResourceLoaderContext();
313 $module = new ResourceLoaderTestModule();
314 $module->setName( 'test' );
315 $this->assertSame( [], $module->getHeaders( $context ), 'Default' );
317 $module = $this->getMockBuilder( ResourceLoaderTestModule
::class )
318 ->onlyMethods( [ 'getPreloadLinks' ] )->getMock();
319 $module->method( 'getPreloadLinks' )->willReturn( [
320 'https://example.org/script.js' => [ 'as' => 'script' ],
324 'Link: <https://example.org/script.js>;rel=preload;as=script'
326 $module->getHeaders( $context ),
327 'Preload one resource'
330 $module = $this->getMockBuilder( ResourceLoaderTestModule
::class )
331 ->onlyMethods( [ 'getPreloadLinks' ] )->getMock();
332 $module->method( 'getPreloadLinks' )->willReturn( [
333 'https://example.org/script.js' => [ 'as' => 'script' ],
334 '/example.png' => [ 'as' => 'image' ],
336 $module->setName( 'test' );
339 'Link: <https://example.org/script.js>;rel=preload;as=script,' .
340 '</example.png>;rel=preload;as=image'
342 $module->getHeaders( $context ),
343 'Preload two resources'
347 public static function provideGetDeprecationWarning() {
357 'This page is using the deprecated ResourceLoader module "deprecatedModule".',
360 'Will be removed tomorrow.',
361 'deprecatedTomorrow',
362 "This page is using the deprecated ResourceLoader module \"deprecatedTomorrow\".\n" .
363 "Will be removed tomorrow.",
369 * @dataProvider provideGetDeprecationWarning
371 * @param string|bool|null $deprecated
372 * @param string $name
373 * @param string $expected
375 public function testGetDeprecationWarning( $deprecated, $name, $expected ) {
376 $module = new ResourceLoaderTestModule( [ 'deprecated' => $deprecated ] );
377 $module->setName( $name );
378 $this->assertSame( $expected, $module->getDeprecationWarning() );
380 $this->hideDeprecated( 'MediaWiki\ResourceLoader\Module::getDeprecationInformation' );
381 $info = $module->getDeprecationInformation( $this->getResourceLoaderContext() );
383 $this->assertSame( '', $info );
385 $this->assertSame( 'mw.log.warn(' . json_encode( $expected ) . ');', $info );