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