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