Merge "mediawiki.user: Improve test suite"
[lhc/web/wiklou.git] / tests / phpunit / includes / resourceloader / ResourceLoaderStartUpModuleTest.php
1 <?php
2
3 class ResourceLoaderStartUpModuleTest extends ResourceLoaderTestCase {
4
5 protected static function expandPlaceholders( $text ) {
6 return strtr( $text, [
7 '{blankVer}' => self::BLANK_VERSION
8 ] );
9 }
10
11 public function provideGetModuleRegistrations() {
12 return [
13 [ [
14 'msg' => 'Empty registry',
15 'modules' => [],
16 'out' => '
17 mw.loader.addSource( {
18 "local": "/w/load.php"
19 } );
20 mw.loader.register( [] );'
21 ] ],
22 [ [
23 'msg' => 'Basic registry',
24 'modules' => [
25 'test.blank' => new ResourceLoaderTestModule(),
26 ],
27 'out' => '
28 mw.loader.addSource( {
29 "local": "/w/load.php"
30 } );
31 mw.loader.register( [
32 [
33 "test.blank",
34 "{blankVer}"
35 ]
36 ] );',
37 ] ],
38 [ [
39 'msg' => 'Omit raw modules from registry',
40 'modules' => [
41 'test.raw' => new ResourceLoaderTestModule( [ 'isRaw' => true ] ),
42 'test.blank' => new ResourceLoaderTestModule(),
43 ],
44 'out' => '
45 mw.loader.addSource( {
46 "local": "/w/load.php"
47 } );
48 mw.loader.register( [
49 [
50 "test.blank",
51 "{blankVer}"
52 ]
53 ] );',
54 ] ],
55 [ [
56 'msg' => 'Version falls back gracefully if getVersionHash throws',
57 'modules' => [
58 'test.fail' => (
59 ( $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
60 ->setMethods( [ 'getVersionHash' ] )->getMock() )
61 && $mock->method( 'getVersionHash' )->will(
62 $this->throwException( new Exception )
63 )
64 ) ? $mock : $mock
65 ],
66 'out' => '
67 mw.loader.addSource( {
68 "local": "/w/load.php"
69 } );
70 mw.loader.register( [
71 [
72 "test.fail",
73 ""
74 ]
75 ] );
76 mw.loader.state( {
77 "test.fail": "error"
78 } );',
79 ] ],
80 [ [
81 'msg' => 'Use version from getVersionHash',
82 'modules' => [
83 'test.version' => (
84 ( $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
85 ->setMethods( [ 'getVersionHash' ] )->getMock() )
86 && $mock->method( 'getVersionHash' )->willReturn( '1234567' )
87 ) ? $mock : $mock
88 ],
89 'out' => '
90 mw.loader.addSource( {
91 "local": "/w/load.php"
92 } );
93 mw.loader.register( [
94 [
95 "test.version",
96 "1234567"
97 ]
98 ] );',
99 ] ],
100 [ [
101 'msg' => 'Re-hash version from getVersionHash if too long',
102 'modules' => [
103 'test.version' => (
104 ( $mock = $this->getMockBuilder( ResourceLoaderTestModule::class )
105 ->setMethods( [ 'getVersionHash' ] )->getMock() )
106 && $mock->method( 'getVersionHash' )->willReturn( '12345678' )
107 ) ? $mock : $mock
108 ],
109 'out' => '
110 mw.loader.addSource( {
111 "local": "/w/load.php"
112 } );
113 mw.loader.register( [
114 [
115 "test.version",
116 "016es8l"
117 ]
118 ] );',
119 ] ],
120 [ [
121 'msg' => 'Group signature',
122 'modules' => [
123 'test.blank' => new ResourceLoaderTestModule(),
124 'test.group.foo' => new ResourceLoaderTestModule( [ 'group' => 'x-foo' ] ),
125 'test.group.bar' => new ResourceLoaderTestModule( [ 'group' => 'x-bar' ] ),
126 ],
127 'out' => '
128 mw.loader.addSource( {
129 "local": "/w/load.php"
130 } );
131 mw.loader.register( [
132 [
133 "test.blank",
134 "{blankVer}"
135 ],
136 [
137 "test.group.foo",
138 "{blankVer}",
139 [],
140 "x-foo"
141 ],
142 [
143 "test.group.bar",
144 "{blankVer}",
145 [],
146 "x-bar"
147 ]
148 ] );'
149 ] ],
150 [ [
151 'msg' => 'Different target (non-test should not be registered)',
152 'modules' => [
153 'test.blank' => new ResourceLoaderTestModule(),
154 'test.target.foo' => new ResourceLoaderTestModule( [ 'targets' => [ 'x-foo' ] ] ),
155 ],
156 'out' => '
157 mw.loader.addSource( {
158 "local": "/w/load.php"
159 } );
160 mw.loader.register( [
161 [
162 "test.blank",
163 "{blankVer}"
164 ]
165 ] );'
166 ] ],
167 [ [
168 'msg' => 'Safemode disabled (default; register all modules)',
169 'modules' => [
170 // Default origin: ORIGIN_CORE_SITEWIDE
171 'test.blank' => new ResourceLoaderTestModule(),
172 'test.core-generated' => new ResourceLoaderTestModule( [
173 'origin' => ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
174 ] ),
175 'test.sitewide' => new ResourceLoaderTestModule( [
176 'origin' => ResourceLoaderModule::ORIGIN_USER_SITEWIDE
177 ] ),
178 'test.user' => new ResourceLoaderTestModule( [
179 'origin' => ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL
180 ] ),
181 ],
182 'out' => '
183 mw.loader.addSource( {
184 "local": "/w/load.php"
185 } );
186 mw.loader.register( [
187 [
188 "test.blank",
189 "{blankVer}"
190 ],
191 [
192 "test.core-generated",
193 "{blankVer}"
194 ],
195 [
196 "test.sitewide",
197 "{blankVer}"
198 ],
199 [
200 "test.user",
201 "{blankVer}"
202 ]
203 ] );'
204 ] ],
205 [ [
206 'msg' => 'Safemode enabled (filter modules with user/site origin)',
207 'extraQuery' => [ 'safemode' => '1' ],
208 'modules' => [
209 // Default origin: ORIGIN_CORE_SITEWIDE
210 'test.blank' => new ResourceLoaderTestModule(),
211 'test.core-generated' => new ResourceLoaderTestModule( [
212 'origin' => ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
213 ] ),
214 'test.sitewide' => new ResourceLoaderTestModule( [
215 'origin' => ResourceLoaderModule::ORIGIN_USER_SITEWIDE
216 ] ),
217 'test.user' => new ResourceLoaderTestModule( [
218 'origin' => ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL
219 ] ),
220 ],
221 'out' => '
222 mw.loader.addSource( {
223 "local": "/w/load.php"
224 } );
225 mw.loader.register( [
226 [
227 "test.blank",
228 "{blankVer}"
229 ],
230 [
231 "test.core-generated",
232 "{blankVer}"
233 ]
234 ] );'
235 ] ],
236 [ [
237 'msg' => 'Foreign source',
238 'sources' => [
239 'example' => [
240 'loadScript' => 'http://example.org/w/load.php',
241 'apiScript' => 'http://example.org/w/api.php',
242 ],
243 ],
244 'modules' => [
245 'test.blank' => new ResourceLoaderTestModule( [ 'source' => 'example' ] ),
246 ],
247 'out' => '
248 mw.loader.addSource( {
249 "local": "/w/load.php",
250 "example": "http://example.org/w/load.php"
251 } );
252 mw.loader.register( [
253 [
254 "test.blank",
255 "{blankVer}",
256 [],
257 null,
258 "example"
259 ]
260 ] );'
261 ] ],
262 [ [
263 'msg' => 'Conditional dependency function',
264 'modules' => [
265 'test.x.core' => new ResourceLoaderTestModule(),
266 'test.x.polyfill' => new ResourceLoaderTestModule( [
267 'skipFunction' => 'return true;'
268 ] ),
269 'test.y.polyfill' => new ResourceLoaderTestModule( [
270 'skipFunction' =>
271 'return !!(' .
272 ' window.JSON &&' .
273 ' JSON.parse &&' .
274 ' JSON.stringify' .
275 ');'
276 ] ),
277 'test.z.foo' => new ResourceLoaderTestModule( [
278 'dependencies' => [
279 'test.x.core',
280 'test.x.polyfill',
281 'test.y.polyfill',
282 ],
283 ] ),
284 ],
285 'out' => '
286 mw.loader.addSource( {
287 "local": "/w/load.php"
288 } );
289 mw.loader.register( [
290 [
291 "test.x.core",
292 "{blankVer}"
293 ],
294 [
295 "test.x.polyfill",
296 "{blankVer}",
297 [],
298 null,
299 null,
300 "return true;"
301 ],
302 [
303 "test.y.polyfill",
304 "{blankVer}",
305 [],
306 null,
307 null,
308 "return !!( window.JSON \u0026\u0026 JSON.parse \u0026\u0026 JSON.stringify);"
309 ],
310 [
311 "test.z.foo",
312 "{blankVer}",
313 [
314 0,
315 1,
316 2
317 ]
318 ]
319 ] );',
320 ] ],
321 [ [
322 // This may seem like an edge case, but a plain MediaWiki core install
323 // with a few extensions installed is likely far more complex than this
324 // even, not to mention an install like Wikipedia.
325 // TODO: Make this even more realistic.
326 'msg' => 'Advanced (everything combined)',
327 'sources' => [
328 'example' => [
329 'loadScript' => 'http://example.org/w/load.php',
330 'apiScript' => 'http://example.org/w/api.php',
331 ],
332 ],
333 'modules' => [
334 'test.blank' => new ResourceLoaderTestModule(),
335 'test.x.core' => new ResourceLoaderTestModule(),
336 'test.x.util' => new ResourceLoaderTestModule( [
337 'dependencies' => [
338 'test.x.core',
339 ],
340 ] ),
341 'test.x.foo' => new ResourceLoaderTestModule( [
342 'dependencies' => [
343 'test.x.core',
344 ],
345 ] ),
346 'test.x.bar' => new ResourceLoaderTestModule( [
347 'dependencies' => [
348 'test.x.core',
349 'test.x.util',
350 ],
351 ] ),
352 'test.x.quux' => new ResourceLoaderTestModule( [
353 'dependencies' => [
354 'test.x.foo',
355 'test.x.bar',
356 'test.x.util',
357 'test.x.unknown',
358 ],
359 ] ),
360 'test.group.foo.1' => new ResourceLoaderTestModule( [
361 'group' => 'x-foo',
362 ] ),
363 'test.group.foo.2' => new ResourceLoaderTestModule( [
364 'group' => 'x-foo',
365 ] ),
366 'test.group.bar.1' => new ResourceLoaderTestModule( [
367 'group' => 'x-bar',
368 ] ),
369 'test.group.bar.2' => new ResourceLoaderTestModule( [
370 'group' => 'x-bar',
371 'source' => 'example',
372 ] ),
373 'test.target.foo' => new ResourceLoaderTestModule( [
374 'targets' => [ 'x-foo' ],
375 ] ),
376 'test.target.bar' => new ResourceLoaderTestModule( [
377 'source' => 'example',
378 'targets' => [ 'x-foo' ],
379 ] ),
380 ],
381 'out' => '
382 mw.loader.addSource( {
383 "local": "/w/load.php",
384 "example": "http://example.org/w/load.php"
385 } );
386 mw.loader.register( [
387 [
388 "test.blank",
389 "{blankVer}"
390 ],
391 [
392 "test.x.core",
393 "{blankVer}"
394 ],
395 [
396 "test.x.util",
397 "{blankVer}",
398 [
399 1
400 ]
401 ],
402 [
403 "test.x.foo",
404 "{blankVer}",
405 [
406 1
407 ]
408 ],
409 [
410 "test.x.bar",
411 "{blankVer}",
412 [
413 2
414 ]
415 ],
416 [
417 "test.x.quux",
418 "{blankVer}",
419 [
420 3,
421 4,
422 "test.x.unknown"
423 ]
424 ],
425 [
426 "test.group.foo.1",
427 "{blankVer}",
428 [],
429 "x-foo"
430 ],
431 [
432 "test.group.foo.2",
433 "{blankVer}",
434 [],
435 "x-foo"
436 ],
437 [
438 "test.group.bar.1",
439 "{blankVer}",
440 [],
441 "x-bar"
442 ],
443 [
444 "test.group.bar.2",
445 "{blankVer}",
446 [],
447 "x-bar",
448 "example"
449 ]
450 ] );'
451 ] ],
452 ];
453 }
454
455 /**
456 * @dataProvider provideGetModuleRegistrations
457 * @covers ResourceLoaderStartUpModule::getModuleRegistrations
458 * @covers ResourceLoaderStartUpModule::compileUnresolvedDependencies
459 * @covers ResourceLoader::makeLoaderRegisterScript
460 */
461 public function testGetModuleRegistrations( $case ) {
462 $extraQuery = $case['extraQuery'] ?? [];
463 $context = $this->getResourceLoaderContext( $extraQuery );
464 $rl = $context->getResourceLoader();
465 if ( isset( $case['sources'] ) ) {
466 $rl->addSource( $case['sources'] );
467 }
468 $rl->register( $case['modules'] );
469 $module = new ResourceLoaderStartUpModule();
470 $out = ltrim( $case['out'], "\n" );
471
472 // Disable log from getModuleRegistrations via MWExceptionHandler
473 // for case where getVersionHash() is expected to throw.
474 $this->setLogger( 'exception', new Psr\Log\NullLogger() );
475
476 $this->assertEquals(
477 self::expandPlaceholders( $out ),
478 $module->getModuleRegistrations( $context ),
479 $case['msg']
480 );
481 }
482
483 public static function provideRegistrations() {
484 return [
485 [ [
486 'test.blank' => new ResourceLoaderTestModule(),
487 'test.min' => new ResourceLoaderTestModule( [
488 'skipFunction' =>
489 'return !!(' .
490 ' window.JSON &&' .
491 ' JSON.parse &&' .
492 ' JSON.stringify' .
493 ');',
494 'dependencies' => [
495 'test.blank',
496 ],
497 ] ),
498 ] ]
499 ];
500 }
501
502 /**
503 * @covers ResourceLoaderStartUpModule::getModuleRegistrations
504 * @dataProvider provideRegistrations
505 */
506 public function testRegistrationsMinified( $modules ) {
507 $this->setMwGlobals( 'wgResourceLoaderDebug', false );
508
509 $context = $this->getResourceLoaderContext();
510 $rl = $context->getResourceLoader();
511 $rl->register( $modules );
512 $module = new ResourceLoaderStartUpModule();
513 $out = 'mw.loader.addSource({"local":"/w/load.php"});' . "\n"
514 . 'mw.loader.register(['
515 . '["test.blank","{blankVer}"],'
516 . '["test.min","{blankVer}",[0],null,null,'
517 . '"return!!(window.JSON\u0026\u0026JSON.parse\u0026\u0026JSON.stringify);"'
518 . ']]);';
519
520 $this->assertEquals(
521 self::expandPlaceholders( $out ),
522 $module->getModuleRegistrations( $context ),
523 'Minified output'
524 );
525 }
526
527 /**
528 * @covers ResourceLoaderStartUpModule::getModuleRegistrations
529 * @dataProvider provideRegistrations
530 */
531 public function testRegistrationsUnminified( $modules ) {
532 $context = $this->getResourceLoaderContext();
533 $rl = $context->getResourceLoader();
534 $rl->register( $modules );
535 $module = new ResourceLoaderStartUpModule();
536 $out =
537 'mw.loader.addSource( {
538 "local": "/w/load.php"
539 } );
540 mw.loader.register( [
541 [
542 "test.blank",
543 "{blankVer}"
544 ],
545 [
546 "test.min",
547 "{blankVer}",
548 [
549 0
550 ],
551 null,
552 null,
553 "return !!( window.JSON \u0026\u0026 JSON.parse \u0026\u0026 JSON.stringify);"
554 ]
555 ] );';
556
557 $this->assertEquals(
558 self::expandPlaceholders( $out ),
559 $module->getModuleRegistrations( $context ),
560 'Unminified output'
561 );
562 }
563
564 /**
565 * @covers ResourceLoaderStartupModule::getDefinitionSummary
566 */
567 public function testGetVersionHash_varyConfig() {
568 $context = $this->getResourceLoaderContext();
569
570 $this->setMwGlobals( 'wgArticlePath', '/w1' );
571 $module = new ResourceLoaderStartupModule();
572 $version1 = $module->getVersionHash( $context );
573 $module = new ResourceLoaderStartupModule();
574 $version2 = $module->getVersionHash( $context );
575
576 $this->setMwGlobals( 'wgArticlePath', '/w3' );
577 $module = new ResourceLoaderStartupModule();
578 $version3 = $module->getVersionHash( $context );
579
580 $this->assertEquals(
581 $version1,
582 $version2,
583 'Deterministic version hash'
584 );
585
586 $this->assertNotEquals(
587 $version1,
588 $version3,
589 'Config change impacts version hash'
590 );
591 }
592
593 /**
594 * @covers ResourceLoaderStartupModule
595 */
596 public function testGetVersionHash_varyModule() {
597 $context1 = $this->getResourceLoaderContext();
598 $rl1 = $context1->getResourceLoader();
599 $rl1->register( [
600 'test.a' => new ResourceLoaderTestModule(),
601 'test.b' => new ResourceLoaderTestModule(),
602 ] );
603 $module = new ResourceLoaderStartupModule();
604 $version1 = $module->getVersionHash( $context1 );
605
606 $context2 = $this->getResourceLoaderContext();
607 $rl2 = $context2->getResourceLoader();
608 $rl2->register( [
609 'test.b' => new ResourceLoaderTestModule(),
610 'test.c' => new ResourceLoaderTestModule(),
611 ] );
612 $module = new ResourceLoaderStartupModule();
613 $version2 = $module->getVersionHash( $context2 );
614
615 $context3 = $this->getResourceLoaderContext();
616 $rl3 = $context3->getResourceLoader();
617 $rl3->register( [
618 'test.a' => new ResourceLoaderTestModule(),
619 'test.b' => new ResourceLoaderTestModule( [ 'script' => 'different' ] ),
620 ] );
621 $module = new ResourceLoaderStartupModule();
622 $version3 = $module->getVersionHash( $context3 );
623
624 // Module name *is* significant (T201686)
625 $this->assertNotEquals(
626 $version1,
627 $version2,
628 'Module name is significant'
629 );
630
631 $this->assertNotEquals(
632 $version1,
633 $version3,
634 'Hash change of any module impacts startup hash'
635 );
636 }
637
638 /**
639 * @covers ResourceLoaderStartupModule
640 */
641 public function testGetVersionHash_varyDeps() {
642 $context = $this->getResourceLoaderContext();
643 $rl = $context->getResourceLoader();
644 $rl->register( [
645 'test.a' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'x', 'y' ] ] ),
646 ] );
647 $module = new ResourceLoaderStartupModule();
648 $version1 = $module->getVersionHash( $context );
649
650 $context = $this->getResourceLoaderContext();
651 $rl = $context->getResourceLoader();
652 $rl->register( [
653 'test.a' => new ResourceLoaderTestModule( [ 'dependencies' => [ 'x', 'z' ] ] ),
654 ] );
655 $module = new ResourceLoaderStartupModule();
656 $version2 = $module->getVersionHash( $context );
657
658 // Dependencies *are* significant (T201686)
659 $this->assertNotEquals(
660 $version1,
661 $version2,
662 'Dependencies are significant'
663 );
664 }
665
666 }