Merge "Use different varname for upgraded hash from original hash"
[lhc/web/wiklou.git] / tests / phpunit / includes / resourceloader / ResourceLoaderTest.php
1 <?php
2
3 class ResourceLoaderTest extends ResourceLoaderTestCase {
4
5 protected function setUp() {
6 parent::setUp();
7
8 $this->setMwGlobals( [
9 'wgResourceLoaderLESSImportPaths' => [
10 dirname( dirname( __DIR__ ) ) . '/data/less/common',
11 ],
12 'wgResourceLoaderLESSVars' => [
13 'foo' => '2px',
14 'Foo' => '#eeeeee',
15 'bar' => 5,
16 ],
17 ] );
18 }
19
20 /**
21 * Ensure the ResourceLoaderRegisterModules hook is called.
22 *
23 * @covers ResourceLoader::__construct
24 */
25 public function testConstructRegistrationHook() {
26 $resourceLoaderRegisterModulesHook = false;
27
28 $this->setMwGlobals( 'wgHooks', [
29 'ResourceLoaderRegisterModules' => [
30 function ( &$resourceLoader ) use ( &$resourceLoaderRegisterModulesHook ) {
31 $resourceLoaderRegisterModulesHook = true;
32 }
33 ]
34 ] );
35
36 $unused = new ResourceLoader();
37 $this->assertTrue(
38 $resourceLoaderRegisterModulesHook,
39 'Hook ResourceLoaderRegisterModules called'
40 );
41 }
42
43 /**
44 * @covers ResourceLoader::register
45 * @covers ResourceLoader::getModule
46 */
47 public function testRegisterValid() {
48 $module = new ResourceLoaderTestModule();
49 $resourceLoader = new EmptyResourceLoader();
50 $resourceLoader->register( 'test', $module );
51 $this->assertEquals( $module, $resourceLoader->getModule( 'test' ) );
52 }
53
54 /**
55 * @covers ResourceLoader::register
56 */
57 public function testRegisterEmptyString() {
58 $module = new ResourceLoaderTestModule();
59 $resourceLoader = new EmptyResourceLoader();
60 $resourceLoader->register( '', $module );
61 $this->assertEquals( $module, $resourceLoader->getModule( '' ) );
62 }
63
64 /**
65 * @covers ResourceLoader::register
66 */
67 public function testRegisterInvalidName() {
68 $resourceLoader = new EmptyResourceLoader();
69 $this->setExpectedException( 'MWException', "name 'test!invalid' is invalid" );
70 $resourceLoader->register( 'test!invalid', new ResourceLoaderTestModule() );
71 }
72
73 /**
74 * @covers ResourceLoader::register
75 */
76 public function testRegisterInvalidType() {
77 $resourceLoader = new EmptyResourceLoader();
78 $this->setExpectedException( 'MWException', 'ResourceLoader module info type error' );
79 $resourceLoader->register( 'test', new stdClass() );
80 }
81
82 /**
83 * @covers ResourceLoader::getModuleNames
84 */
85 public function testGetModuleNames() {
86 // Use an empty one so that core and extension modules don't get in.
87 $resourceLoader = new EmptyResourceLoader();
88 $resourceLoader->register( 'test.foo', new ResourceLoaderTestModule() );
89 $resourceLoader->register( 'test.bar', new ResourceLoaderTestModule() );
90 $this->assertEquals(
91 [ 'test.foo', 'test.bar' ],
92 $resourceLoader->getModuleNames()
93 );
94 }
95
96 /**
97 * @covers ResourceLoader::isModuleRegistered
98 */
99 public function testIsModuleRegistered() {
100 $rl = new EmptyResourceLoader();
101 $rl->register( 'test', new ResourceLoaderTestModule() );
102 $this->assertTrue( $rl->isModuleRegistered( 'test' ) );
103 $this->assertFalse( $rl->isModuleRegistered( 'test.unknown' ) );
104 }
105
106 /**
107 * @covers ResourceLoader::getModule
108 */
109 public function testGetModuleUnknown() {
110 $rl = new EmptyResourceLoader();
111 $this->assertSame( null, $rl->getModule( 'test' ) );
112 }
113
114 /**
115 * @covers ResourceLoader::getModule
116 */
117 public function testGetModuleClass() {
118 $rl = new EmptyResourceLoader();
119 $rl->register( 'test', [ 'class' => ResourceLoaderTestModule::class ] );
120 $this->assertInstanceOf(
121 ResourceLoaderTestModule::class,
122 $rl->getModule( 'test' )
123 );
124 }
125
126 /**
127 * @covers ResourceLoader::getModule
128 */
129 public function testGetModuleClassDefault() {
130 $rl = new EmptyResourceLoader();
131 $rl->register( 'test', [] );
132 $this->assertInstanceOf(
133 ResourceLoaderFileModule::class,
134 $rl->getModule( 'test' ),
135 'Array-style module registrations default to FileModule'
136 );
137 }
138
139 /**
140 * @covers ResourceLoaderFileModule::compileLessFile
141 */
142 public function testLessFileCompilation() {
143 $context = $this->getResourceLoaderContext();
144 $basePath = __DIR__ . '/../../data/less/module';
145 $module = new ResourceLoaderFileModule( [
146 'localBasePath' => $basePath,
147 'styles' => [ 'styles.less' ],
148 ] );
149 $module->setName( 'test.less' );
150 $styles = $module->getStyles( $context );
151 $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
152 }
153
154 public static function providePackedModules() {
155 return [
156 [
157 'Example from makePackedModulesString doc comment',
158 [ 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' ],
159 'foo.bar,baz|bar.baz,quux',
160 ],
161 [
162 'Example from expandModuleNames doc comment',
163 [ 'jquery.foo', 'jquery.bar', 'jquery.ui.baz', 'jquery.ui.quux' ],
164 'jquery.foo,bar|jquery.ui.baz,quux',
165 ],
166 [
167 'Regression fixed in r88706 with dotless names',
168 [ 'foo', 'bar', 'baz' ],
169 'foo,bar,baz',
170 ],
171 [
172 'Prefixless modules after a prefixed module',
173 [ 'single.module', 'foobar', 'foobaz' ],
174 'single.module|foobar,foobaz',
175 ],
176 [
177 'Ordering',
178 [ 'foo', 'foo.baz', 'baz.quux', 'foo.bar' ],
179 'foo|foo.baz,bar|baz.quux',
180 [ 'foo', 'foo.baz', 'foo.bar', 'baz.quux' ],
181 ]
182 ];
183 }
184
185 /**
186 * @dataProvider providePackedModules
187 * @covers ResourceLoader::makePackedModulesString
188 */
189 public function testMakePackedModulesString( $desc, $modules, $packed ) {
190 $this->assertEquals( $packed, ResourceLoader::makePackedModulesString( $modules ), $desc );
191 }
192
193 /**
194 * @dataProvider providePackedModules
195 * @covers ResourceLoaderContext::expandModuleNames
196 */
197 public function testExpandModuleNames( $desc, $modules, $packed, $unpacked = null ) {
198 $this->assertEquals(
199 $unpacked ?: $modules,
200 ResourceLoaderContext::expandModuleNames( $packed ),
201 $desc
202 );
203 }
204
205 public static function provideAddSource() {
206 return [
207 [ 'foowiki', 'https://example.org/w/load.php', 'foowiki' ],
208 [ 'foowiki', [ 'loadScript' => 'https://example.org/w/load.php' ], 'foowiki' ],
209 [
210 [
211 'foowiki' => 'https://example.org/w/load.php',
212 'bazwiki' => 'https://example.com/w/load.php',
213 ],
214 null,
215 [ 'foowiki', 'bazwiki' ]
216 ]
217 ];
218 }
219
220 /**
221 * @dataProvider provideAddSource
222 * @covers ResourceLoader::addSource
223 * @covers ResourceLoader::getSources
224 */
225 public function testAddSource( $name, $info, $expected ) {
226 $rl = new ResourceLoader;
227 $rl->addSource( $name, $info );
228 if ( is_array( $expected ) ) {
229 foreach ( $expected as $source ) {
230 $this->assertArrayHasKey( $source, $rl->getSources() );
231 }
232 } else {
233 $this->assertArrayHasKey( $expected, $rl->getSources() );
234 }
235 }
236
237 /**
238 * @covers ResourceLoader::addSource
239 */
240 public function testAddSourceDupe() {
241 $rl = new ResourceLoader;
242 $this->setExpectedException( 'MWException', 'ResourceLoader duplicate source addition error' );
243 $rl->addSource( 'foo', 'https://example.org/w/load.php' );
244 $rl->addSource( 'foo', 'https://example.com/w/load.php' );
245 }
246
247 /**
248 * @covers ResourceLoader::addSource
249 */
250 public function testAddSourceInvalid() {
251 $rl = new ResourceLoader;
252 $this->setExpectedException( 'MWException', 'with no "loadScript" key' );
253 $rl->addSource( 'foo', [ 'x' => 'https://example.org/w/load.php' ] );
254 }
255
256 public static function provideLoaderImplement() {
257 return [
258 [ [
259 'title' => 'Implement scripts, styles and messages',
260
261 'name' => 'test.example',
262 'scripts' => 'mw.example();',
263 'styles' => [ 'css' => [ '.mw-example {}' ] ],
264 'messages' => [ 'example' => '' ],
265 'templates' => [],
266
267 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
268 mw.example();
269 }, {
270 "css": [
271 ".mw-example {}"
272 ]
273 }, {
274 "example": ""
275 } );',
276 ] ],
277 [ [
278 'title' => 'Implement scripts',
279
280 'name' => 'test.example',
281 'scripts' => 'mw.example();',
282 'styles' => [],
283
284 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
285 mw.example();
286 } );',
287 ] ],
288 [ [
289 'title' => 'Implement styles',
290
291 'name' => 'test.example',
292 'scripts' => [],
293 'styles' => [ 'css' => [ '.mw-example {}' ] ],
294
295 'expected' => 'mw.loader.implement( "test.example", [], {
296 "css": [
297 ".mw-example {}"
298 ]
299 } );',
300 ] ],
301 [ [
302 'title' => 'Implement scripts and messages',
303
304 'name' => 'test.example',
305 'scripts' => 'mw.example();',
306 'messages' => [ 'example' => '' ],
307
308 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
309 mw.example();
310 }, {}, {
311 "example": ""
312 } );',
313 ] ],
314 [ [
315 'title' => 'Implement scripts and templates',
316
317 'name' => 'test.example',
318 'scripts' => 'mw.example();',
319 'templates' => [ 'example.html' => '' ],
320
321 'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery, require, module ) {
322 mw.example();
323 }, {}, {}, {
324 "example.html": ""
325 } );',
326 ] ],
327 [ [
328 'title' => 'Implement unwrapped user script',
329
330 'name' => 'user',
331 'scripts' => 'mw.example( 1 );',
332 'wrap' => false,
333
334 'expected' => 'mw.loader.implement( "user", "mw.example( 1 );" );',
335 ] ],
336 ];
337 }
338
339 /**
340 * @dataProvider provideLoaderImplement
341 * @covers ResourceLoader::makeLoaderImplementScript
342 * @covers ResourceLoader::trimArray
343 */
344 public function testMakeLoaderImplementScript( $case ) {
345 $case += [
346 'wrap' => true,
347 'styles' => [], 'templates' => [], 'messages' => new XmlJsCode( '{}' )
348 ];
349 ResourceLoader::clearCache();
350 $this->setMwGlobals( 'wgResourceLoaderDebug', true );
351
352 $rl = TestingAccessWrapper::newFromClass( 'ResourceLoader' );
353 $this->assertEquals(
354 $case['expected'],
355 $rl->makeLoaderImplementScript(
356 $case['name'],
357 ( $case['wrap'] && is_string( $case['scripts'] ) )
358 ? new XmlJsCode( $case['scripts'] )
359 : $case['scripts'],
360 $case['styles'],
361 $case['messages'],
362 $case['templates']
363 )
364 );
365 }
366
367 /**
368 * @covers ResourceLoader::makeLoaderImplementScript
369 */
370 public function testMakeLoaderImplementScriptInvalid() {
371 $this->setExpectedException( 'MWException', 'Invalid scripts error' );
372 $rl = TestingAccessWrapper::newFromClass( 'ResourceLoader' );
373 $rl->makeLoaderImplementScript(
374 'test', // name
375 123, // scripts
376 null, // styles
377 null, // messages
378 null // templates
379 );
380 }
381
382 /**
383 * @covers ResourceLoader::makeLoaderSourcesScript
384 */
385 public function testMakeLoaderSourcesScript() {
386 $this->assertEquals(
387 'mw.loader.addSource( "local", "/w/load.php" );',
388 ResourceLoader::makeLoaderSourcesScript( 'local', '/w/load.php' )
389 );
390 $this->assertEquals(
391 'mw.loader.addSource( {
392 "local": "/w/load.php"
393 } );',
394 ResourceLoader::makeLoaderSourcesScript( [ 'local' => '/w/load.php' ] )
395 );
396 $this->assertEquals(
397 'mw.loader.addSource( {
398 "local": "/w/load.php",
399 "example": "https://example.org/w/load.php"
400 } );',
401 ResourceLoader::makeLoaderSourcesScript( [
402 'local' => '/w/load.php',
403 'example' => 'https://example.org/w/load.php'
404 ] )
405 );
406 $this->assertEquals(
407 'mw.loader.addSource( [] );',
408 ResourceLoader::makeLoaderSourcesScript( [] )
409 );
410 }
411
412 private static function fakeSources() {
413 return [
414 'examplewiki' => [
415 'loadScript' => '//example.org/w/load.php',
416 'apiScript' => '//example.org/w/api.php',
417 ],
418 'example2wiki' => [
419 'loadScript' => '//example.com/w/load.php',
420 'apiScript' => '//example.com/w/api.php',
421 ],
422 ];
423 }
424
425 /**
426 * @covers ResourceLoader::getLoadScript
427 */
428 public function testGetLoadScript() {
429 $this->setMwGlobals( 'wgResourceLoaderSources', [] );
430 $rl = new ResourceLoader();
431 $sources = self::fakeSources();
432 $rl->addSource( $sources );
433 foreach ( [ 'examplewiki', 'example2wiki' ] as $name ) {
434 $this->assertEquals( $rl->getLoadScript( $name ), $sources[$name]['loadScript'] );
435 }
436
437 try {
438 $rl->getLoadScript( 'thiswasneverreigstered' );
439 $this->assertTrue( false, 'ResourceLoader::getLoadScript should have thrown an exception' );
440 } catch ( MWException $e ) {
441 $this->assertTrue( true );
442 }
443 }
444 }