3 use Wikimedia\TestingAccessWrapper;
12 const SCREEN_MEDIA_QUERY =
'screen and (min-width: 982px)';
13 const SCREEN_ONLY_MEDIA_QUERY =
'only screen and (min-width: 982px)';
16 const RSS_RC_LINK =
'<link rel="alternate" type="application/rss+xml" title=" RSS feed" href="/w/index.php?title=Special:RecentChanges&feed=rss"/>';
17 const ATOM_RC_LINK =
'<link rel="alternate" type="application/atom+xml" title=" Atom feed" href="/w/index.php?title=Special:RecentChanges&feed=atom"/>';
19 const RSS_TEST_LINK =
'<link rel="alternate" type="application/rss+xml" title=""Test" RSS feed" href="fake-link"/>';
20 const ATOM_TEST_LINK =
'<link rel="alternate" type="application/atom+xml" title=""Test" Atom feed" href="fake-link"/>';
24 protected function setUp() {
26 ResourceLoader::clearCache();
30 ResourceLoader::clearCache();
40 public function testRedirect( $url,
$code =
null ) {
41 $op = $this->newInstance();
42 if ( isset(
$code ) ) {
43 $op->redirect( $url,
$code );
45 $op->redirect( $url );
47 $expectedUrl = str_replace(
"\n",
'', $url );
48 $this->assertSame( $expectedUrl, $op->getRedirect() );
49 $this->assertSame( $expectedUrl, $op->mRedirect );
50 $this->assertSame(
$code ??
'302', $op->mRedirectCode );
53 public function provideRedirect() {
55 [
'http://example.com' ],
56 [
'http://example.com',
'400' ],
57 [
'http://example.com',
'squirrels!!!' ],
62 private function setupFeedLinks( $feed, $types ) {
63 $outputPage = $this->newInstance( [
64 'AdvertisedFeedTypes' => $types,
66 'OverrideSiteFeed' =>
false,
72 'wgScript' =>
'/w/index.php',
77 private function assertFeedLinks( $outputPage, $message, $present, $non_present ) {
78 $links = $outputPage->getHeadLinksArray();
80 $this->assertContains(
$link, $links, $message );
82 foreach ( $non_present
as $link ) {
83 $this->assertNotContains(
$link, $links, $message );
87 private function assertFeedUILinks( $outputPage, $ui_links ) {
89 $this->assertTrue( $outputPage->isSyndicated(),
'Syndication should be offered' );
90 $this->assertGreaterThan( 0,
count( $outputPage->getSyndicationLinks() ),
91 'Some syndication links should be there' );
93 $this->assertFalse( $outputPage->isSyndicated(),
'No syndication should be offered' );
94 $this->assertEquals( 0,
count( $outputPage->getSyndicationLinks() ),
95 'No syndication links should be there' );
99 public static function provideFeedLinkData() {
102 true, [
'rss' ],
'Only RSS RC link should be offerred',
103 [ self::RSS_RC_LINK ], [ self::ATOM_RC_LINK ]
106 true, [
'atom' ],
'Only Atom RC link should be offerred',
107 [ self::ATOM_RC_LINK ], [ self::RSS_RC_LINK ]
110 true, [],
'No RC feed formats should be offerred',
111 [], [ self::ATOM_RC_LINK, self::RSS_RC_LINK ]
114 false, [
'atom' ],
'No RC feeds should be offerred',
115 [], [ self::ATOM_RC_LINK, self::RSS_RC_LINK ]
124 public function testSetCopyrightUrl() {
125 $op = $this->newInstance();
126 $op->setCopyrightUrl(
'http://example.com' );
129 Html::element(
'link', [
'rel' =>
'license',
'href' =>
'http://example.com' ] ),
130 $op->getHeadLinksArray()[
'copyright']
138 public function testRecentChangesFeed( $feed, $advertised_feed_types,
139 $message, $present, $non_present ) {
140 $outputPage = $this->setupFeedLinks( $feed, $advertised_feed_types );
141 $this->assertFeedLinks( $outputPage, $message, $present, $non_present );
144 public static function provideAdditionalFeedData() {
147 true, [
'atom' ],
'Additional Atom feed should be offered',
149 [ self::ATOM_TEST_LINK, self::ATOM_RC_LINK ],
150 [ self::RSS_TEST_LINK, self::RSS_RC_LINK ],
154 true, [
'rss' ],
'Additional RSS feed should be offered',
156 [ self::RSS_TEST_LINK, self::RSS_RC_LINK ],
157 [ self::ATOM_TEST_LINK, self::ATOM_RC_LINK ],
161 true, [
'rss' ],
'Additional Atom feed should NOT be offered with RSS enabled',
163 [ self::RSS_RC_LINK ],
164 [ self::RSS_TEST_LINK, self::ATOM_TEST_LINK, self::ATOM_RC_LINK ],
168 false, [
'atom' ],
'Additional Atom feed should NOT be offered, all feeds disabled',
172 self::RSS_TEST_LINK, self::ATOM_TEST_LINK,
173 self::ATOM_RC_LINK, self::ATOM_RC_LINK,
187 public function testAdditionalFeeds( $feed, $advertised_feed_types, $message,
188 $additional_feed_type, $present, $non_present, $any_ui_links ) {
189 $outputPage = $this->setupFeedLinks( $feed, $advertised_feed_types );
190 $outputPage->addFeedLink( $additional_feed_type,
'fake-link' );
191 $this->assertFeedLinks( $outputPage, $message, $present, $non_present );
192 $this->assertFeedUILinks( $outputPage, $any_ui_links );
202 public function testMetaTags() {
203 $op = $this->newInstance();
204 $op->addMeta(
'http:expires',
'0' );
205 $op->addMeta(
'keywords',
'first' );
206 $op->addMeta(
'keywords',
'second' );
207 $op->addMeta(
'og:title',
'Ta-duh' );
210 [
'http:expires',
'0' ],
211 [
'keywords',
'first' ],
212 [
'keywords',
'second' ],
213 [
'og:title',
'Ta-duh' ],
215 $this->assertSame( $expected, $op->getMetaTags() );
217 $links = $op->getHeadLinksArray();
218 $this->assertContains(
'<meta http-equiv="expires" content="0"/>', $links );
219 $this->assertContains(
'<meta name="keywords" content="first"/>', $links );
220 $this->assertContains(
'<meta name="keywords" content="second"/>', $links );
221 $this->assertContains(
'<meta property="og:title" content="Ta-duh"/>', $links );
222 $this->assertArrayNotHasKey(
'meta-robots', $links );
230 public function testAddLink() {
231 $op = $this->newInstance();
235 [
'rel' =>
'foo',
'href' =>
'http://example.com' ],
239 $op->addLink(
$link );
242 $this->assertSame( $links, $op->getLinkTags() );
244 $result = $op->getHeadLinksArray();
247 $this->assertContains( Html::element(
'link',
$link ),
$result );
256 public function testSetCanonicalUrl() {
257 $op = $this->newInstance();
258 $op->setCanonicalUrl(
'http://example.comm' );
259 $op->setCanonicalUrl(
'http://example.com' );
261 $this->assertSame(
'http://example.com', $op->getCanonicalUrl() );
263 $headLinks = $op->getHeadLinksArray();
265 $this->assertContains( Html::element(
'link', [
266 'rel' =>
'canonical',
'href' =>
'http://example.com'
269 $this->assertNotContains( Html::element(
'link', [
270 'rel' =>
'canonical',
'href' =>
'http://example.comm'
277 public function testAddScript() {
278 $op = $this->newInstance();
279 $op->addScript(
'some random string' );
281 $this->assertContains(
"\nsome random string\n",
"\n" . $op->getBottomScripts() .
"\n" );
287 public function testAddScriptFile() {
288 $op = $this->newInstance();
289 $op->addScriptFile(
'/somescript.js' );
290 $op->addScriptFile(
'//example.com/somescript.js' );
292 $this->assertContains(
293 "\n" . Html::linkedScript(
'/somescript.js', $op->getCSPNonce() ) .
294 Html::linkedScript(
'//example.com/somescript.js', $op->getCSPNonce() ) .
"\n",
295 "\n" . $op->getBottomScripts() .
"\n"
304 public function testAddDeprecatedScriptFileWarning() {
306 'Use of OutputPage::addScriptFile was deprecated in MediaWiki 1.24.' );
308 $op = $this->newInstance();
309 $op->addScriptFile(
'ignored-script.js' );
318 public function testAddDeprecatedScriptFileNoOp() {
320 $op = $this->newInstance();
321 $op->addScriptFile(
'ignored-script.js' );
323 $this->assertNotContains(
'ignored-script.js',
'' . $op->getBottomScripts() );
329 public function testAddInlineScript() {
330 $op = $this->newInstance();
331 $op->addInlineScript(
'let foo = "bar";' );
332 $op->addInlineScript(
'alert( foo );' );
334 $this->assertContains(
335 "\n" . Html::inlineScript(
"\nlet foo = \"bar\";\n", $op->getCSPNonce() ) .
"\n" .
336 Html::inlineScript(
"\nalert( foo );\n", $op->getCSPNonce() ) .
"\n",
337 "\n" . $op->getBottomScripts() .
"\n"
347 public function testSetTarget() {
348 $op = $this->newInstance();
349 $op->setTarget(
'foo' );
351 $this->assertSame(
'foo', $op->getTarget() );
363 public function testHeadItems() {
364 $op = $this->newInstance();
365 $op->addHeadItem(
'a',
'b' );
366 $op->addHeadItems( [
'c' =>
'<d>&',
'e' =>
'f',
'a' =>
'q' ] );
367 $op->addHeadItem(
'e',
'g' );
368 $op->addHeadItems(
'x' );
370 $this->assertSame( [
'a' =>
'q',
'c' =>
'<d>&',
'e' =>
'g',
'x' ],
371 $op->getHeadItemsArray() );
373 $this->assertTrue( $op->hasHeadItem(
'a' ) );
374 $this->assertTrue( $op->hasHeadItem(
'c' ) );
375 $this->assertTrue( $op->hasHeadItem(
'e' ) );
376 $this->assertTrue( $op->hasHeadItem(
'0' ) );
378 $this->assertContains(
"\nq\n<d>&\ng\nx\n",
379 '' . $op->headElement( $op->getContext()->getSkin() ) );
387 public function testHeadItemsParserOutput() {
388 $op = $this->newInstance();
389 $stubPO1 = $this->createParserOutputStub(
'getHeadItems', [
'a' =>
'b' ] );
390 $op->addParserOutputMetadata( $stubPO1 );
391 $stubPO2 = $this->createParserOutputStub(
'getHeadItems',
392 [
'c' =>
'<d>&',
'e' =>
'f',
'a' =>
'q' ] );
393 $op->addParserOutputMetadata( $stubPO2 );
394 $stubPO3 = $this->createParserOutputStub(
'getHeadItems', [
'e' =>
'g' ] );
395 $op->addParserOutput( $stubPO3 );
396 $stubPO4 = $this->createParserOutputStub(
'getHeadItems', [
'x' ] );
397 $op->addParserOutputMetadata( $stubPO4 );
399 $this->assertSame( [
'a' =>
'q',
'c' =>
'<d>&',
'e' =>
'g',
'x' ],
400 $op->getHeadItemsArray() );
402 $this->assertTrue( $op->hasHeadItem(
'a' ) );
403 $this->assertTrue( $op->hasHeadItem(
'c' ) );
404 $this->assertTrue( $op->hasHeadItem(
'e' ) );
405 $this->assertTrue( $op->hasHeadItem(
'0' ) );
406 $this->assertFalse( $op->hasHeadItem(
'b' ) );
408 $this->assertContains(
"\nq\n<d>&\ng\nx\n",
409 '' . $op->headElement( $op->getContext()->getSkin() ) );
415 public function testAddBodyClasses() {
416 $op = $this->newInstance();
417 $op->addBodyClasses(
'a' );
418 $op->addBodyClasses(
'mediawiki' );
419 $op->addBodyClasses(
'b c' );
420 $op->addBodyClasses( [
'd',
'e' ] );
421 $op->addBodyClasses(
'a' );
423 $this->assertContains(
'"a mediawiki b c d e ltr',
424 '' . $op->headElement( $op->getContext()->getSkin() ) );
431 public function testArticleBodyOnly() {
432 $op = $this->newInstance();
433 $this->assertFalse( $op->getArticleBodyOnly() );
435 $op->setArticleBodyOnly(
true );
436 $this->assertTrue( $op->getArticleBodyOnly() );
438 $op->addHTML(
'<b>a</b>' );
440 $this->assertSame(
'<b>a</b>', $op->output(
true ) );
447 public function testProperties() {
448 $op = $this->newInstance();
450 $this->assertNull( $op->getProperty(
'foo' ) );
452 $op->setProperty(
'foo',
'bar' );
453 $op->setProperty(
'baz',
'quz' );
455 $this->assertSame(
'bar', $op->getProperty(
'foo' ) );
456 $this->assertSame(
'quz', $op->getProperty(
'baz' ) );
465 public function testCheckLastModified(
466 $timestamp, $ifModifiedSince, $expected, $config = [], $callback =
null
469 if ( $ifModifiedSince ) {
470 if ( is_numeric( $ifModifiedSince ) ) {
472 $ifModifiedSince = date(
'D, d M Y H:i:s', $ifModifiedSince ) .
' GMT';
474 $request->setHeader(
'If-Modified-Since', $ifModifiedSince );
477 if ( !isset( $config[
'CacheEpoch'] ) ) {
479 $config[
'CacheEpoch'] =
'20000101000000';
482 $op = $this->newInstance( $config,
$request );
485 $callback( $op, $this );
489 Wikimedia\suppressWarnings();
491 $this->assertEquals( $expected, $op->checkLastModified( $timestamp ) );
493 Wikimedia\restoreWarnings();
497 public function provideCheckLastModified() {
498 $lastModified = time() - 3600;
501 [
'0', $lastModified,
false ],
502 'Timestamp Unix epoch' =>
503 [
'19700101000000', $lastModified,
false ],
504 'Timestamp same as If-Modified-Since' =>
505 [ $lastModified, $lastModified,
true ],
506 'Timestamp one second after If-Modified-Since' =>
507 [ $lastModified + 1, $lastModified,
false ],
508 'No If-Modified-Since' =>
509 [ $lastModified + 1,
null,
false ],
510 'Malformed If-Modified-Since' =>
511 [ $lastModified + 1,
'GIBBERING WOMBATS !!!',
false ],
512 'Non-standard IE-style If-Modified-Since' =>
513 [ $lastModified, date(
'D, d M Y H:i:s', $lastModified ) .
' GMT; length=5202',
516 'If-Modified-Since not per spec but we accept it anyway because strtotime does' =>
517 [ $lastModified,
"@$lastModified",
true ],
518 '$wgCachePages = false' =>
519 [ $lastModified, $lastModified,
false, [
'CachePages' =>
false ] ],
521 [ $lastModified, $lastModified,
false,
522 [
'CacheEpoch' =>
wfTimestamp( TS_MW, $lastModified + 1 ) ] ],
523 'Recently-touched user' =>
524 [ $lastModified, $lastModified,
false, [],
526 $op->getContext()->setUser( $this->
getTestUser()->getUser() );
528 'After Squid expiry' =>
529 [ $lastModified, $lastModified,
false,
530 [
'UseSquid' =>
true,
'SquidMaxage' => 3599 ] ],
531 'Hook allows cache use' =>
532 [ $lastModified + 1, $lastModified,
true, [],
533 function ( $op, $that ) {
534 $that->setTemporaryHook(
'OutputPageCheckLastModified',
535 function ( &$modifiedTimes ) {
536 $modifiedTimes = [ 1 ];
540 'Hooks prohibits cache use' =>
541 [ $lastModified, $lastModified,
false, [],
542 function ( $op, $that ) {
543 $that->setTemporaryHook(
'OutputPageCheckLastModified',
544 function ( &$modifiedTimes ) {
545 $modifiedTimes = [ max( $modifiedTimes ) + 1 ];
557 public function testCdnCacheEpoch(
$params ) {
558 $out = TestingAccessWrapper::newFromObject( $this->newInstance() );
559 $reqTime = strtotime(
$params[
'reqTime'] );
560 $pageTime = strtotime(
$params[
'pageTime'] );
561 $actual = max( $pageTime,
$out->getCdnCacheEpoch( $reqTime,
$params[
'maxAge'] ) );
565 gmdate( DateTime::ATOM, $actual ),
570 public static function provideCdnCacheEpoch() {
572 'pageTime' =>
'2011-04-01T12:00:00+00:00',
573 'maxAge' => 24 * 3600,
576 'after 1s' => [
$base + [
577 'reqTime' =>
'2011-04-01T12:00:01+00:00',
578 'expect' =>
'2011-04-01T12:00:00+00:00',
580 'after 23h' => [
$base + [
581 'reqTime' =>
'2011-04-02T11:00:00+00:00',
582 'expect' =>
'2011-04-01T12:00:00+00:00',
584 'after 24h and a bit' => [
$base + [
585 'reqTime' =>
'2011-04-02T12:34:56+00:00',
586 'expect' =>
'2011-04-01T12:34:56+00:00',
588 'after a year' => [
$base + [
589 'reqTime' =>
'2012-05-06T00:12:07+00:00',
590 'expect' =>
'2012-05-05T00:12:07+00:00',
601 public function testSetRobotPolicy() {
602 $op = $this->newInstance();
603 $op->setRobotPolicy(
'noindex, nofollow' );
605 $links = $op->getHeadLinksArray();
606 $this->assertContains(
'<meta name="robots" content="noindex,nofollow"/>', $links );
614 public function testSetIndexFollowPolicies() {
615 $op = $this->newInstance();
616 $op->setIndexPolicy(
'noindex' );
617 $op->setFollowPolicy(
'nofollow' );
619 $links = $op->getHeadLinksArray();
620 $this->assertContains(
'<meta name="robots" content="noindex,nofollow"/>', $links );
623 private function extractHTMLTitle( OutputPage $op ) {
624 $html = $op->headElement( $op->getContext()->getSkin() );
630 $this->assertLessThanOrEqual( 1,
count(
$matches[1] ),
'More than one <title>!' );
642 private static function getMsgText( $op, ...$msgParams ) {
643 return $op->msg( ...$msgParams )->inContentLanguage()->text();
650 public function testHTMLTitle() {
651 $op = $this->newInstance();
654 $this->assertSame(
'', $op->getHTMLTitle() );
655 $this->assertSame(
'', $op->getPageTitle() );
657 $this->getMsgText( $op,
'pagetitle',
'' ),
658 $this->extractHTMLTitle( $op )
662 $op->setHTMLTitle(
'Potatoes will eat me' );
664 $this->assertSame(
'Potatoes will eat me', $op->getHTMLTitle() );
665 $this->assertSame(
'Potatoes will eat me', $this->extractHTMLTitle( $op ) );
667 $this->assertSame(
'', $op->getPageTitle() );
670 $msg = $op->msg(
'mainpage' );
672 $op->setHTMLTitle( $msg );
673 $this->assertSame( $msg->text(), $op->getHTMLTitle() );
674 $this->assertSame( $msg->text(), $this->extractHTMLTitle( $op ) );
675 $this->assertSame(
'', $op->getPageTitle() );
681 public function testSetRedirectedFrom() {
682 $op = $this->newInstance();
685 $this->assertSame(
'Talk:Some_page', $op->getJSVars()[
'wgRedirectedFrom'] );
692 public function testPageTitle() {
694 $op = $this->newInstance();
697 $this->assertSame(
'', $op->getPageTitle() );
698 $this->assertSame(
'', $op->getHTMLTitle() );
701 $op->setPageTitle(
'foobar' );
703 $this->assertSame(
'foobar', $op->getPageTitle() );
705 $this->assertSame( $this->getMsgText( $op,
'pagetitle',
'foobar' ), $op->getHTMLTitle() );
709 $op->setPageTitle(
'<script>a</script>&<i>b</i>' );
711 $this->assertSame(
'<script>a</script>&<i>b</i>', $op->getPageTitle() );
713 $this->getMsgText( $op,
'pagetitle',
'<script>a</script>&b' ),
718 $text = $this->getMsgText( $op,
'mainpage' );
720 $op->setPageTitle( $op->msg(
'mainpage' )->inContentLanguage() );
721 $this->assertSame( $text, $op->getPageTitle() );
722 $this->assertSame( $this->getMsgText( $op,
'pagetitle', $text ), $op->getHTMLTitle() );
728 public function testSetTitle() {
729 $op = $this->newInstance();
731 $this->assertSame(
'My test page', $op->getTitle()->getPrefixedText() );
735 $this->assertSame(
'Another test page', $op->getTitle()->getPrefixedText() );
744 public function testSubtitle() {
745 $op = $this->newInstance();
747 $this->assertSame(
'', $op->getSubtitle() );
749 $op->addSubtitle(
'<b>foo</b>' );
751 $this->assertSame(
'<b>foo</b>', $op->getSubtitle() );
753 $op->addSubtitle( $op->msg(
'mainpage' )->inContentLanguage() );
756 "<b>foo</b><br />\n\t\t\t\t" . $this->getMsgText( $op,
'mainpage' ),
760 $op->setSubtitle(
'There can be only one' );
762 $this->assertSame(
'There can be only one', $op->getSubtitle() );
764 $op->clearSubtitle();
766 $this->assertSame(
'', $op->getSubtitle() );
774 public function testBuildBacklinkSubtitle(
$titles,
$queries, $contains, $notContains ) {
777 $this->assertTrue(
true );
785 $this->
editPage(
'Page 2',
'#REDIRECT [[Page 1]]' );
787 $str = OutputPage::buildBacklinkSubtitle(
$title,
$query )->text();
789 foreach ( $contains
as $substr ) {
790 $this->assertContains( $substr, $str );
793 foreach ( $notContains
as $substr ) {
794 $this->assertNotContains( $substr, $str );
804 public function testAddBacklinkSubtitle(
$titles,
$queries, $contains, $notContains ) {
806 $this->
editPage(
'Page 2',
'#REDIRECT [[Page 1]]' );
808 $op = $this->newInstance();
813 $str = $op->getSubtitle();
815 foreach ( $contains
as $substr ) {
816 $this->assertContains( $substr, $str );
819 foreach ( $notContains
as $substr ) {
820 $this->assertNotContains( $substr, $str );
824 public function provideBacklinkSubtitle() {
830 [
'redirect',
'Page 2' ],
840 [ [
'action' =>
'edit' ] ],
845 [
'Page 1',
'Page 2' ],
847 [
'Page 1',
'Page 2',
"<br />\n\t\t\t\t" ],
858 public function testPrintable() {
859 $op = $this->newInstance();
861 $this->assertFalse( $op->isPrintable() );
865 $this->assertTrue( $op->isPrintable() );
872 public function testDisable() {
873 $op = $this->newInstance();
875 $this->assertFalse( $op->isDisabled() );
876 $this->assertNotSame(
'', $op->output(
true ) );
880 $this->assertTrue( $op->isDisabled() );
881 $this->assertSame(
'', $op->output(
true ) );
889 public function testShowNewSectionLink() {
890 $op = $this->newInstance();
892 $this->assertFalse( $op->showNewSectionLink() );
894 $pOut1 = $this->createParserOutputStub(
'getNewSection',
true );
895 $op->addParserOutputMetadata( $pOut1 );
896 $this->assertTrue( $op->showNewSectionLink() );
898 $pOut2 = $this->createParserOutputStub(
'getNewSection',
false );
899 $op->addParserOutput( $pOut2 );
900 $this->assertFalse( $op->showNewSectionLink() );
908 public function testForceHideNewSectionLink() {
909 $op = $this->newInstance();
911 $this->assertFalse( $op->forceHideNewSectionLink() );
913 $pOut1 = $this->createParserOutputStub(
'getHideNewSection',
true );
914 $op->addParserOutputMetadata( $pOut1 );
915 $this->assertTrue( $op->forceHideNewSectionLink() );
917 $pOut2 = $this->createParserOutputStub(
'getHideNewSection',
false );
918 $op->addParserOutput( $pOut2 );
919 $this->assertFalse( $op->forceHideNewSectionLink() );
926 public function testSetSyndicated() {
927 $op = $this->newInstance( [
'Feed' =>
true ] );
928 $this->assertFalse( $op->isSyndicated() );
930 $op->setSyndicated();
931 $this->assertTrue( $op->isSyndicated() );
933 $op->setSyndicated(
false );
934 $this->assertFalse( $op->isSyndicated() );
936 $op = $this->newInstance();
937 $this->assertFalse( $op->isSyndicated() );
939 $op->setSyndicated();
940 $this->assertFalse( $op->isSyndicated() );
949 public function testFeedLinks() {
950 $op = $this->newInstance( [
'Feed' =>
true ] );
951 $this->assertSame( [], $op->getSyndicationLinks() );
953 $op->addFeedLink(
'not a supported format',
'abc' );
954 $this->assertFalse( $op->isSyndicated() );
955 $this->assertSame( [], $op->getSyndicationLinks() );
957 $feedTypes = $op->getConfig()->get(
'AdvertisedFeedTypes' );
959 $op->addFeedLink( $feedTypes[0],
'def' );
960 $this->assertTrue( $op->isSyndicated() );
961 $this->assertSame( [ $feedTypes[0] =>
'def' ], $op->getSyndicationLinks() );
963 $op->setFeedAppendQuery(
false );
965 foreach ( $feedTypes
as $type ) {
966 $expected[
$type] = $op->getTitle()->getLocalURL(
"feed=$type" );
968 $this->assertSame( $expected, $op->getSyndicationLinks() );
970 $op->setFeedAppendQuery(
'apples=oranges' );
971 foreach ( $feedTypes
as $type ) {
972 $expected[
$type] = $op->getTitle()->getLocalURL(
"feed=$type&apples=oranges" );
974 $this->assertSame( $expected, $op->getSyndicationLinks() );
976 $op = $this->newInstance();
977 $this->assertSame( [], $op->getSyndicationLinks() );
979 $op->addFeedLink( $feedTypes[0],
'def' );
980 $this->assertFalse( $op->isSyndicated() );
981 $this->assertSame( [], $op->getSyndicationLinks() );
990 function testArticleFlags() {
991 $op = $this->newInstance();
992 $this->assertFalse( $op->isArticle() );
993 $this->assertTrue( $op->isArticleRelated() );
995 $op->setArticleRelated(
false );
996 $this->assertFalse( $op->isArticle() );
997 $this->assertFalse( $op->isArticleRelated() );
999 $op->setArticleFlag(
true );
1000 $this->assertTrue( $op->isArticle() );
1001 $this->assertTrue( $op->isArticleRelated() );
1003 $op->setArticleFlag(
false );
1004 $this->assertFalse( $op->isArticle() );
1005 $this->assertTrue( $op->isArticleRelated() );
1007 $op->setArticleFlag(
true );
1008 $op->setArticleRelated(
false );
1009 $this->assertFalse( $op->isArticle() );
1010 $this->assertFalse( $op->isArticleRelated() );
1020 function testLanguageLinks() {
1021 $op = $this->newInstance();
1022 $this->assertSame( [], $op->getLanguageLinks() );
1024 $op->addLanguageLinks( [
'fr:A',
'it:B' ] );
1025 $this->assertSame( [
'fr:A',
'it:B' ], $op->getLanguageLinks() );
1027 $op->addLanguageLinks( [
'de:C',
'es:D' ] );
1028 $this->assertSame( [
'fr:A',
'it:B',
'de:C',
'es:D' ], $op->getLanguageLinks() );
1030 $op->setLanguageLinks( [
'pt:E' ] );
1031 $this->assertSame( [
'pt:E' ], $op->getLanguageLinks() );
1033 $pOut1 = $this->createParserOutputStub(
'getLanguageLinks', [
'he:F',
'ar:G' ] );
1034 $op->addParserOutputMetadata( $pOut1 );
1035 $this->assertSame( [
'pt:E',
'he:F',
'ar:G' ], $op->getLanguageLinks() );
1037 $pOut2 = $this->createParserOutputStub(
'getLanguageLinks', [
'pt:H' ] );
1038 $op->addParserOutput( $pOut2 );
1039 $this->assertSame( [
'pt:E',
'he:F',
'ar:G',
'pt:H' ], $op->getLanguageLinks() );
1059 public function testAddCategoryLinks(
1060 array $args,
array $fakeResults, callable $variantLinkCallback =
null,
1063 $expectedNormal = $this->extractExpectedCategories( $expectedNormal,
'add' );
1064 $expectedHidden = $this->extractExpectedCategories( $expectedHidden,
'add' );
1066 $op = $this->setupCategoryTests( $fakeResults, $variantLinkCallback );
1068 $op->addCategoryLinks(
$args );
1070 $this->doCategoryAsserts( $op, $expectedNormal, $expectedHidden );
1071 $this->doCategoryLinkAsserts( $op, $expectedNormal, $expectedHidden );
1081 public function testAddCategoryLinksOneByOne(
1082 array $args,
array $fakeResults, callable $variantLinkCallback =
null,
1087 $this->assertTrue(
true );
1091 $expectedNormal = $this->extractExpectedCategories( $expectedNormal,
'onebyone' );
1092 $expectedHidden = $this->extractExpectedCategories( $expectedHidden,
'onebyone' );
1094 $op = $this->setupCategoryTests( $fakeResults, $variantLinkCallback );
1096 foreach (
$args as $key => $val ) {
1097 $op->addCategoryLinks( [ $key => $val ] );
1100 $this->doCategoryAsserts( $op, $expectedNormal, $expectedHidden );
1101 $this->doCategoryLinkAsserts( $op, $expectedNormal, $expectedHidden );
1111 public function testSetCategoryLinks(
1112 array $args,
array $fakeResults, callable $variantLinkCallback =
null,
1115 $expectedNormal = $this->extractExpectedCategories( $expectedNormal,
'set' );
1116 $expectedHidden = $this->extractExpectedCategories( $expectedHidden,
'set' );
1118 $op = $this->setupCategoryTests( $fakeResults, $variantLinkCallback );
1120 $op->setCategoryLinks( [
'Initial page' =>
'Initial page' ] );
1121 $op->setCategoryLinks(
$args );
1124 $expectedNormalCats = array_merge( [
'Initial page' ], $expectedNormal );
1125 $expectedCats = array_merge( $expectedHidden, $expectedNormalCats );
1127 $this->doCategoryAsserts( $op, $expectedNormalCats, $expectedHidden );
1128 $this->doCategoryLinkAsserts( $op, $expectedNormal, $expectedHidden );
1139 public function testParserOutputCategoryLinks(
1140 array $args,
array $fakeResults, callable $variantLinkCallback =
null,
1143 $expectedNormal = $this->extractExpectedCategories( $expectedNormal,
'pout' );
1144 $expectedHidden = $this->extractExpectedCategories( $expectedHidden,
'pout' );
1146 $op = $this->setupCategoryTests( $fakeResults, $variantLinkCallback );
1148 $stubPO = $this->createParserOutputStub(
'getCategories',
$args );
1154 $method = [
'addParserOutputMetadata',
'addParserOutput' ][$idx % 2];
1155 $op->$method( $stubPO );
1157 $this->doCategoryAsserts( $op, $expectedNormal, $expectedHidden );
1158 $this->doCategoryLinkAsserts( $op, $expectedNormal, $expectedHidden );
1166 private function extractExpectedCategories(
array $expected, $key ) {
1167 if ( !$expected || isset( $expected[0] ) ) {
1170 return $expected[$key] ?? $expected[
'default'];
1173 private function setupCategoryTests(
1174 array $fakeResults, callable $variantLinkCallback =
null
1180 ->setMethods( [
'addCategoryLinksToLBAndGetResult',
'getTitle' ] )
1184 $op->expects( $this->
any() )
1185 ->method(
'getTitle' )
1186 ->will( $this->returnValue(
$title ) );
1188 $op->expects( $this->
any() )
1189 ->method(
'addCategoryLinksToLBAndGetResult' )
1190 ->will( $this->returnCallback(
function (
array $categories )
use ( $fakeResults ) {
1192 foreach ( $categories
as $category => $unused ) {
1193 if ( isset( $fakeResults[$category] ) ) {
1194 $return[] = $fakeResults[$category];
1197 return new FakeResultWrapper( $return );
1200 if ( $variantLinkCallback ) {
1202 ->setConstructorArgs( [
'en' ] )
1203 ->setMethods( [
'findVariantLink' ] )
1205 $mockContLang->expects( $this->
any() )
1206 ->method(
'findVariantLink' )
1207 ->will( $this->returnCallback( $variantLinkCallback ) );
1211 $this->assertSame( [], $op->getCategories() );
1216 private function doCategoryAsserts( $op, $expectedNormal, $expectedHidden ) {
1217 $this->assertSame( array_merge( $expectedHidden, $expectedNormal ), $op->getCategories() );
1218 $this->assertSame( $expectedNormal, $op->getCategories(
'normal' ) );
1219 $this->assertSame( $expectedHidden, $op->getCategories(
'hidden' ) );
1222 private function doCategoryLinkAsserts( $op, $expectedNormal, $expectedHidden ) {
1223 $catLinks = $op->getCategoryLinks();
1224 $this->assertSame( (
bool)$expectedNormal + (
bool)$expectedHidden,
count( $catLinks ) );
1225 if ( $expectedNormal ) {
1226 $this->assertSame(
count( $expectedNormal ),
count( $catLinks[
'normal'] ) );
1228 if ( $expectedHidden ) {
1229 $this->assertSame(
count( $expectedHidden ),
count( $catLinks[
'hidden'] ) );
1232 foreach ( $expectedNormal
as $i =>
$name ) {
1233 $this->assertContains(
$name, $catLinks[
'normal'][$i] );
1235 foreach ( $expectedHidden
as $i =>
$name ) {
1236 $this->assertContains(
$name, $catLinks[
'hidden'][$i] );
1240 public function provideGetCategories() {
1242 'No categories' => [ [], [],
null, [], [] ],
1244 [
'Test1' =>
'Some sortkey',
'Test2' =>
'A different sortkey' ],
1245 [
'Test1' => (
object)[
'pp_value' => 1,
'page_title' =>
'Test1' ],
1246 'Test2' => (
object)[
'page_title' =>
'Test2' ] ],
1251 'Invalid title' => [
1252 [
'[' =>
'[',
'Test' =>
'Test' ],
1253 [
'Test' => (
object)[
'page_title' =>
'Test' ] ],
1259 [
'Test' =>
'Test',
'Estay' =>
'Estay' ],
1260 [
'Test' => (
object)[
'page_title' =>
'Test' ] ],
1262 if (
$link ===
'Estay' ) {
1269 [
'onebyone' => [
'Test',
'Test' ],
'default' => [
'Test' ] ],
1278 public function testGetCategoriesInvalid() {
1280 'Invalid category type given: hiddne' );
1282 $op = $this->newInstance();
1283 $op->getCategories(
'hiddne' );
1295 public function testIndicators() {
1296 $op = $this->newInstance();
1297 $this->assertSame( [], $op->getIndicators() );
1299 $op->setIndicators( [] );
1300 $this->assertSame( [], $op->getIndicators() );
1303 $op->setIndicators( [
'b' =>
'x',
'a' =>
'y' ] );
1304 $this->assertSame( [
'a' =>
'y',
'b' =>
'x' ], $op->getIndicators() );
1307 $op->setIndicators( [
'c' =>
'z',
'a' =>
'w' ] );
1308 $this->assertSame( [
'a' =>
'w',
'b' =>
'x',
'c' =>
'z' ], $op->getIndicators() );
1311 $pOut1 = $this->createParserOutputStub(
'getIndicators', [
'c' =>
'u',
'd' =>
'v' ] );
1312 $op->addParserOutputMetadata( $pOut1 );
1313 $this->assertSame( [
'a' =>
'w',
'b' =>
'x',
'c' =>
'u',
'd' =>
'v' ],
1314 $op->getIndicators() );
1317 $pOut2 = $this->createParserOutputStub(
'getIndicators', [
'a' =>
'!!!' ] );
1318 $op->addParserOutput( $pOut2 );
1319 $this->assertSame( [
'a' =>
'!!!',
'b' =>
'x',
'c' =>
'u',
'd' =>
'v' ],
1320 $op->getIndicators() );
1327 public function testAddHelpLink() {
1328 $op = $this->newInstance();
1330 $op->addHelpLink(
'Manual:PHP unit testing' );
1331 $indicators = $op->getIndicators();
1332 $this->assertSame( [
'mw-helplink' ], array_keys( $indicators ) );
1333 $this->assertContains(
'Manual:PHP_unit_testing', $indicators[
'mw-helplink'] );
1335 $op->addHelpLink(
'https://phpunit.de',
true );
1336 $indicators = $op->getIndicators();
1337 $this->assertSame( [
'mw-helplink' ], array_keys( $indicators ) );
1338 $this->assertContains(
'https://phpunit.de', $indicators[
'mw-helplink'] );
1339 $this->assertNotContains(
'mediawiki', $indicators[
'mw-helplink'] );
1340 $this->assertNotContains(
'Manual:PHP', $indicators[
'mw-helplink'] );
1350 public function testBodyHTML() {
1351 $op = $this->newInstance();
1352 $this->assertSame(
'', $op->getHTML() );
1354 $op->addHTML(
'a' );
1355 $this->assertSame(
'a', $op->getHTML() );
1357 $op->addHTML(
'b' );
1358 $this->assertSame(
'ab', $op->getHTML() );
1360 $op->prependHTML(
'c' );
1361 $this->assertSame(
'cab', $op->getHTML() );
1363 $op->addElement(
'p', [
'id' =>
'foo' ],
'd' );
1364 $this->assertSame(
'cab<p id="foo">d</p>', $op->getHTML() );
1367 $this->assertSame(
'', $op->getHTML() );
1375 public function testRevisionId( $newVal, $expected ) {
1376 $op = $this->newInstance();
1378 $this->assertNull( $op->setRevisionId( $newVal ) );
1379 $this->assertSame( $expected, $op->getRevisionId() );
1380 $this->assertSame( $expected, $op->setRevisionId(
null ) );
1381 $this->assertNull( $op->getRevisionId() );
1384 public function provideRevisionId() {
1391 [
'32% finished', 32 ],
1400 public function testRevisionTimestamp() {
1401 $op = $this->newInstance();
1402 $this->assertNull( $op->getRevisionTimestamp() );
1404 $this->assertNull( $op->setRevisionTimestamp(
'abc' ) );
1405 $this->assertSame(
'abc', $op->getRevisionTimestamp() );
1406 $this->assertSame(
'abc', $op->setRevisionTimestamp(
null ) );
1407 $this->assertNull( $op->getRevisionTimestamp() );
1414 public function testFileVersion() {
1415 $op = $this->newInstance();
1416 $this->assertNull( $op->getFileVersion() );
1419 $stubFile->method(
'exists' )->willReturn(
true );
1420 $stubFile->method(
'getTimestamp' )->willReturn(
'12211221123321' );
1421 $stubFile->method(
'getSha1' )->willReturn(
'bf3ffa7047dc080f5855377a4f83cd18887e3b05' );
1423 $op->setFileVersion( $stubFile );
1425 $this->assertEquals(
1426 [
'time' =>
'12211221123321',
'sha1' =>
'bf3ffa7047dc080f5855377a4f83cd18887e3b05' ],
1427 $op->getFileVersion()
1430 $stubMissingFile = $this->createMock(
File::class );
1431 $stubMissingFile->method(
'exists' )->willReturn(
false );
1433 $op->setFileVersion( $stubMissingFile );
1434 $this->assertNull( $op->getFileVersion() );
1436 $op->setFileVersion( $stubFile );
1437 $this->assertNotNull( $op->getFileVersion() );
1439 $op->setFileVersion(
null );
1440 $this->assertNull( $op->getFileVersion() );
1447 private function createParserOutputStub( ...
$args ) {
1451 $retVals =
$args[0];
1456 foreach ( $retVals
as $method => $retVal ) {
1457 $pOut->method( $method )->willReturn( $retVal );
1460 $arrayReturningMethods = [
1462 'getFileSearchOptions',
1470 foreach ( $arrayReturningMethods
as $method ) {
1471 $pOut->method( $method )->willReturn( [] );
1482 public function testTemplateIds() {
1483 $op = $this->newInstance();
1484 $this->assertSame( [], $op->getTemplateIds() );
1487 $stubPOEmpty = $this->createParserOutputStub();
1488 $op->addParserOutputMetadata( $stubPOEmpty );
1489 $this->assertSame( [], $op->getTemplateIds() );
1493 NS_MAIN => [
'A' => 3,
'B' => 17 ],
1498 $stubPO1 = $this->createParserOutputStub(
'getTemplateIds', $ids );
1500 $op->addParserOutputMetadata( $stubPO1 );
1501 $this->assertSame( $ids, $op->getTemplateIds() );
1504 $stubPO2 = $this->createParserOutputStub(
'getTemplateIds', [
1510 NS_MAIN => [
'E' => 1234,
'A' => 3,
'B' => 17 ],
1516 $op->addParserOutput( $stubPO2 );
1517 $this->assertSame( $finalIds, $op->getTemplateIds() );
1520 $op->addParserOutputMetadata( $stubPOEmpty );
1521 $this->assertSame( $finalIds, $op->getTemplateIds() );
1529 public function testFileSearchOptions() {
1530 $op = $this->newInstance();
1531 $this->assertSame( [], $op->getFileSearchOptions() );
1534 $stubPOEmpty = $this->createParserOutputStub();
1536 $op->addParserOutputMetadata( $stubPOEmpty );
1537 $this->assertSame( [], $op->getFileSearchOptions() );
1541 'A' => [
'time' =>
null,
'sha1' =>
'' ],
1543 'time' =>
'12211221123321',
1544 'sha1' =>
'bf3ffa7047dc080f5855377a4f83cd18887e3b05',
1548 $stubPO1 = $this->createParserOutputStub(
'getFileSearchOptions', $files1 );
1550 $op->addParserOutput( $stubPO1 );
1551 $this->assertSame( $files1, $op->getFileSearchOptions() );
1555 'C' => [
'time' =>
null,
'sha1' =>
'' ],
1556 'B' => [
'time' =>
null,
'sha1' =>
'' ],
1559 $stubPO2 = $this->createParserOutputStub(
'getFileSearchOptions', $files2 );
1561 $op->addParserOutputMetadata( $stubPO2 );
1562 $this->assertSame( array_merge( $files1, $files2 ), $op->getFileSearchOptions() );
1565 $op->addParserOutput( $stubPOEmpty );
1566 $this->assertSame( array_merge( $files1, $files2 ), $op->getFileSearchOptions() );
1581 public function testAddWikiText( $method,
array $args, $expected ) {
1582 $op = $this->newInstance();
1583 $this->assertSame(
'', $op->getHTML() );
1594 [
'addWikiTextWithTitle',
'addWikiTextTitleTidy',
'addWikiTextTitle' ]
1597 $args[1] = $op->getTitle();
1601 [
'addWikiTextAsInterface',
'addWikiTextAsContent' ]
1604 $args[2] = $op->getTitle();
1607 $op->$method( ...
$args );
1608 $this->assertSame( $expected, $op->getHTML() );
1611 public function provideAddWikiText() {
1615 'Simple wikitext' => [
1617 "<p><b>Bold</b>\n</p>",
1618 ],
'List at start' => [
1620 "<ul><li>List</li></ul>\n",
1621 ],
'List not at start' => [
1622 [
'* Not a list',
false ],
1624 ],
'Non-interface' => [
1625 [
"'''Bold'''",
true,
false ],
1626 "<p><b>Bold</b>\n</p>",
1627 ],
'No section edit links' => [
1629 "<h2><span class=\"mw-headline\" id=\"Title\">Title</span></h2>",
1632 'addWikiTextWithTitle' => [
1634 'With title at start' => [
1636 "<ul><li>Some page</li></ul>\n",
1637 ],
'With title at start' => [
1642 'addWikiTextAsInterface' => [
1644 'Simple wikitext' => [
1646 "<p><b>Bold</b>\n</p>",
1647 ],
'Untidy wikitext' => [
1649 "<p><b>Bold\n</b></p>",
1650 ],
'List at start' => [
1652 "<ul><li>List</li></ul>\n",
1653 ],
'List not at start' => [
1654 [
'* Not a list',
false ],
1655 '<p>* Not a list</p>',
1656 ],
'No section edit links' => [
1658 "<h2><span class=\"mw-headline\" id=\"Title\">Title</span></h2>",
1659 ],
'With title at start' => [
1661 "<ul><li>Some page</li></ul>\n",
1662 ],
'With title at start' => [
1664 "<p>* Some page</p>",
1665 ],
'Untidy input' => [
1667 "<p><b>Some page\n</b></p>",
1670 'addWikiTextAsContent' => [
1672 'SpecialNewimages' => [
1673 [
"<p lang='en' dir='ltr'>\nMy message" ],
1674 '<p lang="en" dir="ltr">' .
"\nMy message</p>"
1675 ],
'List at start' => [
1677 "<ul><li>List</li></ul>",
1678 ],
'List not at start' => [
1679 [
'* <b>Not a list',
false ],
1680 '<p>* <b>Not a list</b></p>',
1681 ],
'With title at start' => [
1683 "<ul><li>Some page</li></ul>\n",
1684 ],
'With title at start' => [
1686 "<p>* Some page</p>",
1688 [
"<div class='mw-editintro'>{{PAGENAME}}",
true,
Title::newFromText(
'Talk:Some page' ) ],
1689 '<div class="mw-editintro">' .
"Some page</div>"
1692 'wrapWikiTextAsInterface' => [
1694 [
'wrapperClass',
'text' ],
1695 "<div class=\"wrapperClass\"><p>text\n</p></div>"
1696 ],
'Spurious </div>' => [
1697 [
'wrapperClass',
'text</div><div>more' ],
1698 "<div class=\"wrapperClass\"><p>text</p><div>more</div></div>"
1699 ],
'Extra newlines would break <p> wrappers' => [
1700 [
'two classes',
"1\n\n2\n\n3" ],
1701 "<div class=\"two classes\"><p>1\n</p><p>2\n</p><p>3\n</p></div>"
1702 ],
'Other unclosed tags' => [
1703 [
'error',
'a<b>c<i>d' ],
1704 "<div class=\"error\"><p>a<b>c<i>d\n</i></b></p></div>"
1710 foreach ( $tests[
'addWikiText']
as $key => $val ) {
1711 $args = [ $val[0][0],
null, $val[0][1] ??
true,
false, $val[0][2] ??
true ];
1712 $tests[
'addWikiTextTitle'][
"$key (addWikiTextTitle)"] =
1713 array_merge( [
$args ], array_slice( $val, 1 ) );
1715 foreach ( $tests[
'addWikiTextWithTitle']
as $key => $val ) {
1716 $args = [ $val[0][0], $val[0][1], $val[0][2] ??
true ];
1717 $tests[
'addWikiTextTitle'][
"$key (addWikiTextTitle)"] =
1718 array_merge( [
$args ], array_slice( $val, 1 ) );
1720 foreach ( $tests[
'addWikiTextAsInterface']
as $key => $val ) {
1721 $args = [ $val[0][0], $val[0][2] ??
null, $val[0][1] ??
true,
true,
true ];
1722 $tests[
'addWikiTextTitle'][
"$key (addWikiTextTitle)"] =
1723 array_merge( [
$args ], array_slice( $val, 1 ) );
1725 foreach ( $tests[
'addWikiTextAsContent']
as $key => $val ) {
1726 $args = [ $val[0][0], $val[0][2] ??
null, $val[0][1] ??
true,
true,
false ];
1727 $tests[
'addWikiTextTitle'][
"$key (addWikiTextTitle)"] =
1728 array_merge( [
$args ], array_slice( $val, 1 ) );
1732 foreach ( $tests[
'addWikiTextAsContent']
as $key => $val ) {
1733 if (
count( $val[0] ) > 2 ) {
1734 $args = [ $val[0][0], $val[0][2], $val[0][1] ??
true ];
1735 $tests[
'addWikiTextTitleTidy'][
"$key (addWikiTextTitleTidy)"] =
1736 array_merge( [
$args ], array_slice( $val, 1 ) );
1738 $args = [ $val[0][0], $val[0][1] ??
true ];
1739 $tests[
'addWikiTextTidy'][
"$key (addWikiTextTidy)"] =
1740 array_merge( [
$args ], array_slice( $val, 1 ) );
1746 foreach ( $tests
as $key => $subarray ) {
1747 foreach ( $subarray
as $subkey => $val ) {
1748 $val = array_merge( [ $key ], $val );
1749 $ret[$subkey] = $val;
1759 public function testAddWikiTextNoTitle() {
1763 $op = $this->newInstance( [],
null,
'notitle' );
1764 $op->addWikiText(
'a' );
1770 public function testAddWikiTextAsInterfaceNoTitle() {
1773 $op = $this->newInstance( [],
null,
'notitle' );
1774 $op->addWikiTextAsInterface(
'a' );
1780 public function testAddWikiTextAsContentNoTitle() {
1783 $op = $this->newInstance( [],
null,
'notitle' );
1784 $op->addWikiTextAsContent(
'a' );
1790 public function testAddWikiMsg() {
1792 $this->assertSame(
'(a)', $msg->rawParams(
'a' )->plain() );
1794 $op = $this->newInstance();
1795 $this->assertSame(
'', $op->getHTML() );
1796 $op->addWikiMsg(
'parentheses',
"<b>a" );
1798 $this->assertSame(
"<p>(<b>a)\n</b></p>", $op->getHTML() );
1804 public function testWrapWikiMsg() {
1806 $this->assertSame(
'(a)', $msg->rawParams(
'a' )->plain() );
1808 $op = $this->newInstance();
1809 $this->assertSame(
'', $op->getHTML() );
1810 $op->wrapWikiMsg(
'[$1]', [
'parentheses',
"<b>a" ] );
1812 $this->assertSame(
"<p>[(<b>a)]\n</b></p>", $op->getHTML() );
1819 public function testNoGallery() {
1820 $op = $this->newInstance();
1821 $this->assertFalse( $op->mNoGallery );
1823 $stubPO1 = $this->createParserOutputStub(
'getNoGallery',
true );
1824 $op->addParserOutputMetadata( $stubPO1 );
1825 $this->assertTrue( $op->mNoGallery );
1827 $stubPO2 = $this->createParserOutputStub(
'getNoGallery',
false );
1828 $op->addParserOutput( $stubPO2 );
1829 $this->assertFalse( $op->mNoGallery );
1832 private static $parserOutputHookCalled;
1837 public function testParserOutputHooks() {
1838 $op = $this->newInstance();
1839 $pOut = $this->createParserOutputStub(
'getOutputHooks', [
1840 [
'myhook',
'banana' ],
1841 [
'yourhook',
'kumquat' ],
1842 [
'theirhook',
'hippopotamus' ],
1845 self::$parserOutputHookCalled = [];
1849 use ( $op, $pOut ) {
1850 $this->assertSame( $op, $innerOp );
1851 $this->assertSame( $pOut, $innerPOut );
1852 $this->assertSame(
'banana',
$data );
1853 self::$parserOutputHookCalled[] =
'closure';
1855 'yourhook' => [ $this,
'parserOutputHookCallback' ],
1856 'theirhook' => [ __CLASS__,
'parserOutputHookCallbackStatic' ],
1857 'uncalled' =>
function () {
1858 $this->assertTrue(
false );
1862 $op->addParserOutputMetadata( $pOut );
1864 $this->assertSame( [
'closure',
'callback',
'static' ], self::$parserOutputHookCalled );
1867 public function parserOutputHookCallback(
1870 $this->assertSame(
'kumquat',
$data );
1872 self::$parserOutputHookCalled[] =
'callback';
1875 public static function parserOutputHookCallbackStatic(
1879 self::assertSame(
'hippopotamus',
$data );
1881 self::$parserOutputHookCalled[] =
'static';
1896 public function testAddParserOutputText() {
1897 $op = $this->newInstance();
1898 $this->assertSame(
'', $op->getHTML() );
1900 $pOut = $this->createParserOutputStub(
'getText',
'<some text>' );
1902 $op->addParserOutputMetadata( $pOut );
1903 $this->assertSame(
'', $op->getHTML() );
1905 $op->addParserOutputText( $pOut );
1906 $this->assertSame(
'<some text>', $op->getHTML() );
1912 public function testAddParserOutput() {
1913 $op = $this->newInstance();
1914 $this->assertSame(
'', $op->getHTML() );
1915 $this->assertFalse( $op->showNewSectionLink() );
1917 $pOut = $this->createParserOutputStub( [
1918 'getText' =>
'<some text>',
1919 'getNewSection' =>
true,
1922 $op->addParserOutput( $pOut );
1923 $this->assertSame(
'<some text>', $op->getHTML() );
1924 $this->assertTrue( $op->showNewSectionLink() );
1930 public function testAddTemplate() {
1932 $template->method(
'getHTML' )->willReturn(
'<abc>&def;' );
1934 $op = $this->newInstance();
1937 $this->assertSame(
'<abc>&def;', $op->getHTML() );
1947 public function testParse(
array $args, $expectedHTML ) {
1949 $op = $this->newInstance();
1950 $this->assertSame( $expectedHTML, $op->parse( ...$args ) );
1957 public function testParseInline(
array $args, $expectedHTML, $expectedHTMLInline =
null ) {
1960 $this->assertTrue(
true );
1964 $op = $this->newInstance();
1965 $this->assertSame( $expectedHTMLInline ?? $expectedHTML, $op->parseInline( ...$args ) );
1968 public function provideParse() {
1970 'List at start of line (content)' => [
1971 [
'* List',
true,
false ],
1972 "<div class=\"mw-parser-output\"><ul><li>List</li></ul></div>",
1973 "<ul><li>List</li></ul>",
1975 'List at start of line (interface)' => [
1976 [
'* List',
true,
true ],
1977 "<ul><li>List</li></ul>",
1979 'List not at start (content)' => [
1980 [
"* ''Not'' list",
false,
false ],
1981 '<div class="mw-parser-output">* <i>Not</i> list</div>',
1982 '* <i>Not</i> list',
1984 'List not at start (interface)' => [
1985 [
"* ''Not'' list",
false,
true ],
1986 '* <i>Not</i> list',
1988 'Interface message' => [
1989 [
"''Italic''",
true,
true ],
1990 "<p><i>Italic</i>\n</p>",
1993 'formatnum (content)' => [
1994 [
'{{formatnum:123456.789}}',
true,
false ],
1995 "<div class=\"mw-parser-output\"><p>123,456.789\n</p></div>",
1998 'formatnum (interface)' => [
1999 [
'{{formatnum:123456.789}}',
true,
true ],
2000 "<p>123,456.789\n</p>",
2003 'Language (content)' => [
2005 "<div class=\"mw-parser-output\"><p>123.456,789\n</p></div>",
2007 'Language (interface)' => [
2009 "<p>123.456,789\n</p>",
2012 'No section edit links' => [
2014 '<div class="mw-parser-output"><h2><span class="mw-headline" id="Header">' .
2015 "Header</span></h2></div>",
2016 '<h2><span class="mw-headline" id="Header">Header</span></h2>',
2028 public function testParseAsContent(
2029 array $args, $expectedHTML, $expectedHTMLInline =
null
2031 $op = $this->newInstance();
2032 $this->assertSame( $expectedHTML, $op->parseAsContent( ...$args ) );
2042 public function testParseAsInterface(
2043 array $args, $expectedHTML, $expectedHTMLInline =
null
2045 $op = $this->newInstance();
2046 $this->assertSame( $expectedHTML, $op->parseAsInterface( ...$args ) );
2053 public function testParseInlineAsInterface(
2054 array $args, $expectedHTML, $expectedHTMLInline =
null
2056 $op = $this->newInstance();
2058 $expectedHTMLInline ?? $expectedHTML,
2059 $op->parseInlineAsInterface( ...$args )
2063 public function provideParseAs() {
2065 'List at start of line' => [
2067 "<ul><li>List</li></ul>",
2069 'List not at start' => [
2070 [
"* ''Not'' list",
false ],
2071 '<p>* <i>Not</i> list</p>',
2072 '* <i>Not</i> list',
2075 [
"''Italic''",
true ],
2076 "<p><i>Italic</i>\n</p>",
2080 [
'{{formatnum:123456.789}}',
true ],
2081 "<p>123,456.789\n</p>",
2084 'No section edit links' => [
2086 '<h2><span class="mw-headline" id="Header">Header</span></h2>',
2094 public function testParseNullTitle() {
2096 $this->setExpectedException(
MWException::class,
'Empty $mTitle in OutputPage::parseInternal' );
2097 $op = $this->newInstance( [],
null,
'notitle' );
2104 public function testParseInlineNullTitle() {
2106 $this->setExpectedException(
MWException::class,
'Empty $mTitle in OutputPage::parseInternal' );
2107 $op = $this->newInstance( [],
null,
'notitle' );
2108 $op->parseInline(
'' );
2114 public function testParseAsContentNullTitle() {
2115 $this->setExpectedException(
MWException::class,
'Empty $mTitle in OutputPage::parseInternal' );
2116 $op = $this->newInstance( [],
null,
'notitle' );
2117 $op->parseAsContent(
'' );
2123 public function testParseAsInterfaceNullTitle() {
2124 $this->setExpectedException(
MWException::class,
'Empty $mTitle in OutputPage::parseInternal' );
2125 $op = $this->newInstance( [],
null,
'notitle' );
2126 $op->parseAsInterface(
'' );
2132 public function testParseInlineAsInterfaceNullTitle() {
2133 $this->setExpectedException(
MWException::class,
'Empty $mTitle in OutputPage::parseInternal' );
2134 $op = $this->newInstance( [],
null,
'notitle' );
2135 $op->parseInlineAsInterface(
'' );
2142 public function testCdnMaxage() {
2143 $op = $this->newInstance();
2144 $wrapper = TestingAccessWrapper::newFromObject( $op );
2145 $this->assertSame( 0, $wrapper->mCdnMaxage );
2147 $op->setCdnMaxage( -1 );
2148 $this->assertSame( -1, $wrapper->mCdnMaxage );
2150 $op->setCdnMaxage( 120 );
2151 $this->assertSame( 120, $wrapper->mCdnMaxage );
2153 $op->setCdnMaxage( 60 );
2154 $this->assertSame( 60, $wrapper->mCdnMaxage );
2156 $op->setCdnMaxage( 180 );
2157 $this->assertSame( 180, $wrapper->mCdnMaxage );
2159 $op->lowerCdnMaxage( 240 );
2160 $this->assertSame( 180, $wrapper->mCdnMaxage );
2162 $op->setCdnMaxage( 300 );
2163 $this->assertSame( 240, $wrapper->mCdnMaxage );
2165 $op->lowerCdnMaxage( 120 );
2166 $this->assertSame( 120, $wrapper->mCdnMaxage );
2168 $op->setCdnMaxage( 180 );
2169 $this->assertSame( 120, $wrapper->mCdnMaxage );
2171 $op->setCdnMaxage( 60 );
2172 $this->assertSame( 60, $wrapper->mCdnMaxage );
2174 $op->setCdnMaxage( 240 );
2175 $this->assertSame( 120, $wrapper->mCdnMaxage );
2179 private static $fakeTime;
2191 MWTimestamp::setFakeTime( self::$fakeTime );
2193 $op = $this->newInstance();
2196 $initial =
$options[
'initialMaxage'] ?? 86400;
2197 $op->setCdnMaxage( $initial );
2199 $op->adaptCdnTTL( ...
$args );
2201 MWTimestamp::setFakeTime(
false );
2204 $wrapper = TestingAccessWrapper::newFromObject( $op );
2207 if (
$args[0] ===
null ||
$args[0] ===
false ) {
2208 $this->assertSame( $initial, $wrapper->mCdnMaxage,
'member value' );
2209 $op->setCdnMaxage( $expected + 1 );
2210 $this->assertSame( $expected + 1, $wrapper->mCdnMaxage,
'member value after new set' );
2214 $this->assertSame( $expected, $wrapper->mCdnMaxageLimit,
'limit value' );
2216 if ( $initial >= $expected ) {
2217 $this->assertSame( $expected, $wrapper->mCdnMaxage,
'member value' );
2219 $this->assertSame( $initial, $wrapper->mCdnMaxage,
'member value' );
2222 $op->setCdnMaxage( $expected + 1 );
2223 $this->assertSame( $expected, $wrapper->mCdnMaxage,
'member value after new set' );
2226 public function provideAdaptCdnTTL() {
2229 self::$fakeTime = $now;
2231 'Five minutes ago' => [ [ $now - 300 ], 270 ],
2234 'Five minutes ago, initial maxage four minutes' =>
2235 [ [ $now - 300 ], 270, [
'initialMaxage' => 240 ] ],
2236 'A very long time ago' => [ [ $now - 1000000000 ],
$wgSquidMaxage ],
2237 'Initial maxage zero' => [ [ $now - 300 ], 270, [
'initialMaxage' => 0 ] ],
2248 'Now, minTTL 0.000001' => [ [ $now, 0.000001 ], 0 ],
2249 'A very long time ago, maxTTL even longer' =>
2250 [ [ $now - 1000000000, 0, 1000000001 ], 900000000 ],
2259 public function testClientCache() {
2260 $op = $this->newInstance();
2263 $this->assertSame(
true, $op->enableClientCache(
null ) );
2265 $this->assertSame(
true, $op->enableClientCache(
null ) );
2268 $this->assertSame(
true, $op->enableClientCache(
false ) );
2269 $this->assertSame(
false, $op->enableClientCache(
null ) );
2271 $this->assertSame(
false, $op->enableClientCache(
null ) );
2274 $pOutCacheable = $this->createParserOutputStub(
'isCacheable',
true );
2275 $op->addParserOutputMetadata( $pOutCacheable );
2276 $this->assertSame(
false, $op->enableClientCache(
null ) );
2279 $this->assertSame(
false, $op->enableClientCache(
true ) );
2280 $this->assertSame(
true, $op->enableClientCache(
null ) );
2283 $pOutUncacheable = $this->createParserOutputStub(
'isCacheable',
false );
2284 $op->addParserOutput( $pOutUncacheable );
2285 $this->assertSame(
false, $op->enableClientCache(
null ) );
2291 public function testGetCacheVaryCookies() {
2293 $op = $this->newInstance();
2295 $expectedCookies = [
2297 "{$prefix}LoggedOut",
2298 "{$prefix}_session",
2305 TestingAccessWrapper::newFromClass(
OutputPage::class )->cacheVaryCookies =
null;
2307 $this->
setMwGlobals(
'wgCacheVaryCookies', [
'cookie1' ] );
2309 function ( $innerOP, &$cookies )
use ( $op, $expectedCookies ) {
2310 $this->assertSame( $op, $innerOP );
2311 $cookies[] =
'cookie2';
2312 $this->assertSame( $expectedCookies, $cookies );
2316 $this->assertSame( $expectedCookies, $op->getCacheVaryCookies() );
2322 public function testHaveCacheVaryCookies() {
2324 $op = $this->newInstance( [],
$request );
2327 $this->assertFalse( $op->haveCacheVaryCookies() );
2330 $request->setCookie(
'Token',
'' );
2331 $this->assertFalse( $op->haveCacheVaryCookies() );
2334 $request->setCookie(
'Token',
'123' );
2335 $this->assertTrue( $op->haveCacheVaryCookies() );
2350 public function testVaryHeaders(
array $calls,
array $cookies, $vary, $key ) {
2354 ->setMethods( [
'getCacheVaryCookies' ] )
2356 $op->expects( $this->
any() )
2357 ->method(
'getCacheVaryCookies' )
2358 ->will( $this->returnValue( $cookies ) );
2359 TestingAccessWrapper::newFromObject( $op )->mVaryHeader = [];
2362 foreach ( $calls
as $call ) {
2363 $op->addVaryHeader( ...$call );
2365 $this->assertEquals( $vary, $op->getVaryHeader(),
'Vary:' );
2366 $this->assertEquals( $key, $op->getKeyHeader(),
'Key:' );
2369 public function provideVaryHeaders() {
2378 'Single header' => [
2386 'Non-unique headers' => [
2389 [
'Accept-Language' ],
2393 'Vary: Cookie, Accept-Language',
2394 'Key: Cookie,Accept-Language',
2396 'Two headers with single options' => [
2398 [
'Cookie', [
'param=phpsessid' ] ],
2399 [
'Accept-Language', [
'substr=en' ] ],
2402 'Vary: Cookie, Accept-Language',
2403 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
2405 'One header with multiple options' => [
2407 [
'Cookie', [
'param=phpsessid',
'param=userId' ] ],
2411 'Key: Cookie;param=phpsessid;param=userId',
2413 'Duplicate option' => [
2415 [
'Cookie', [
'param=phpsessid' ] ],
2416 [
'Cookie', [
'param=phpsessid' ] ],
2417 [
'Accept-Language', [
'substr=en',
'substr=en' ] ],
2420 'Vary: Cookie, Accept-Language',
2421 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
2423 'Same header, different options' => [
2425 [
'Cookie', [
'param=phpsessid' ] ],
2426 [
'Cookie', [
'param=userId' ] ],
2430 'Key: Cookie;param=phpsessid;param=userId',
2432 'No header, vary cookies' => [
2434 [
'cookie1',
'cookie2' ],
2436 'Key: Cookie;param=cookie1;param=cookie2',
2438 'Cookie header with option plus vary cookies' => [
2440 [
'Cookie', [
'param=cookie1' ] ],
2442 [
'cookie2',
'cookie3' ],
2444 'Key: Cookie;param=cookie1;param=cookie2;param=cookie3',
2446 'Non-cookie header plus vary cookies' => [
2448 [
'Accept-Language' ],
2451 'Vary: Accept-Language, Cookie',
2452 'Key: Accept-Language,Cookie;param=cookie',
2454 'Cookie and non-cookie headers plus vary cookies' => [
2456 [
'Cookie', [
'param=cookie1' ] ],
2457 [
'Accept-Language' ],
2460 'Vary: Cookie, Accept-Language',
2461 'Key: Cookie;param=cookie1;param=cookie2,Accept-Language',
2469 public function testVaryHeaderDefault() {
2470 $op = $this->newInstance();
2471 $this->assertSame(
'Vary: Accept-Encoding, Cookie', $op->getVaryHeader() );
2480 public function testLinkHeaders(
array $headers,
$result ) {
2481 $op = $this->newInstance();
2484 $op->addLinkHeader(
$header );
2487 $this->assertEquals(
$result, $op->getLinkHeader() );
2490 public function provideLinkHeaders() {
2497 [
'<https://foo/bar.jpg>;rel=preload;as=image' ],
2498 'Link: <https://foo/bar.jpg>;rel=preload;as=image',
2502 '<https://foo/bar.jpg>;rel=preload;as=image',
2503 '<https://foo/baz.jpg>;rel=preload;as=image'
2505 'Link: <https://foo/bar.jpg>;rel=preload;as=image,<https://foo/baz.jpg>;' .
2506 'rel=preload;as=image',
2516 public function testAddAcceptLanguage(
2520 $op = $this->newInstance( [],
$req, in_array(
'notitle',
$options ) ?
'notitle' :
null );
2522 if ( !in_array(
'notitle',
$options ) ) {
2525 if ( in_array(
'varianturl',
$options ) ) {
2526 $mockLang->expects( $this->never() )->method( $this->
anything() );
2528 $mockLang->method(
'hasVariants' )->willReturn(
count( $variants ) > 1 );
2529 $mockLang->method(
'getVariants' )->willReturn( $variants );
2530 $mockLang->method(
'getCode' )->willReturn(
$code );
2534 $mockTitle->method(
'getPageLanguage' )->willReturn( $mockLang );
2536 $op->setTitle( $mockTitle );
2540 $op->sendCacheControl();
2543 $keyHeader = $op->getKeyHeader();
2546 $this->assertFalse( strpos(
'Accept-Language', $keyHeader ) );
2550 $keyHeader = explode(
' ', $keyHeader, 2 )[1];
2551 $keyHeader = explode(
',', $keyHeader );
2553 $acceptLanguage =
null;
2554 foreach ( $keyHeader
as $item ) {
2555 if ( strpos( $item,
'Accept-Language;' ) === 0 ) {
2556 $acceptLanguage = $item;
2561 $expectedString =
'Accept-Language;substr=' . implode(
';substr=', $expected );
2562 $this->assertSame( $expectedString, $acceptLanguage );
2565 public function provideAddAcceptLanguage() {
2567 'No variants' => [
'en', [
'en' ], [] ],
2568 'One simple variant' => [
'en', [
'en',
'en-x-piglatin' ], [
'en-x-piglatin' ] ],
2569 'Multiple variants with BCP47 alternatives' => [
2571 [
'zh',
'zh-hans',
'zh-cn',
'zh-tw' ],
2572 [
'zh-hans',
'zh-Hans',
'zh-cn',
'zh-Hans-CN',
'zh-tw',
'zh-Hant-TW' ],
2574 'No title' => [
'en', [
'en',
'en-x-piglatin' ], [], [
'notitle' ] ],
2575 'Variant in URL' => [
'en', [
'en',
'en-x-piglatin' ], [], [
'varianturl' ] ],
2586 public function testClickjacking() {
2587 $op = $this->newInstance();
2588 $this->assertTrue( $op->getPreventClickjacking() );
2590 $op->allowClickjacking();
2591 $this->assertFalse( $op->getPreventClickjacking() );
2593 $op->preventClickjacking();
2594 $this->assertTrue( $op->getPreventClickjacking() );
2596 $op->preventClickjacking(
false );
2597 $this->assertFalse( $op->getPreventClickjacking() );
2599 $pOut1 = $this->createParserOutputStub(
'preventClickjacking',
true );
2600 $op->addParserOutputMetadata( $pOut1 );
2601 $this->assertTrue( $op->getPreventClickjacking() );
2604 $pOut2 = $this->createParserOutputStub(
'preventClickjacking',
false );
2605 $op->addParserOutputMetadata( $pOut2 );
2606 $this->assertTrue( $op->getPreventClickjacking() );
2609 $op->allowClickjacking();
2610 $this->assertFalse( $op->getPreventClickjacking() );
2612 $op->addParserOutput( $pOut1 );
2613 $this->assertTrue( $op->getPreventClickjacking() );
2615 $op->addParserOutput( $pOut2 );
2616 $this->assertTrue( $op->getPreventClickjacking() );
2624 public function testGetFrameOptions(
2625 $breakFrames, $preventClickjacking, $editPageFrameOptions, $expected
2627 $op = $this->newInstance( [
2628 'BreakFrames' => $breakFrames,
2629 'EditPageFrameOptions' => $editPageFrameOptions,
2631 $op->preventClickjacking( $preventClickjacking );
2633 $this->assertSame( $expected, $op->getFrameOptions() );
2636 public function provideGetFrameOptions() {
2638 'BreakFrames true' => [
true,
false,
false,
'DENY' ],
2639 'Allow clickjacking locally' => [
false,
false,
'DENY',
false ],
2640 'Allow clickjacking globally' => [
false,
true,
false,
false ],
2641 'DENY globally' => [
false,
true,
'DENY',
'DENY' ],
2642 'SAMEORIGIN' => [
false,
true,
'SAMEORIGIN',
'SAMEORIGIN' ],
2643 'BreakFrames with SAMEORIGIN' => [
true,
true,
'SAMEORIGIN',
'DENY' ],
2654 public function testMakeResourceLoaderLink(
$args, $expectedHtml ) {
2656 'wgResourceLoaderDebug' =>
false,
2657 'wgLoadScript' =>
'http://127.0.0.1:8080/w/load.php',
2658 'wgCSPReportOnlyHeader' =>
true,
2661 $method = $class->getMethod(
'makeResourceLoaderLink' );
2662 $method->setAccessible(
true );
2665 $ctx->setLanguage(
'en' );
2666 $out =
new OutputPage( $ctx );
2667 $nonce = $class->getProperty(
'CSPNonce' );
2668 $nonce->setAccessible(
true );
2669 $nonce->setValue(
$out,
'secret' );
2670 $rl =
$out->getResourceLoader();
2674 'script' =>
'mw.test.foo( { a: true } );',
2675 'styles' =>
'.mw-test-foo { content: "style"; }',
2678 'script' =>
'mw.test.bar( { a: true } );',
2679 'styles' =>
'.mw-test-bar { content: "style"; }',
2682 'script' =>
'mw.test.baz( { a: true } );',
2683 'styles' =>
'.mw-test-baz { content: "style"; }',
2686 'script' =>
'mw.test.baz( { token: 123 } );',
2687 'styles' =>
'/* pref-animate=off */ .mw-icon { transition: none; }',
2688 'group' =>
'private',
2691 'styles' =>
'.stuff { color: red; }',
2692 'group' =>
'noscript',
2695 'script' =>
'mw.doStuff( "foo" );',
2699 'script' =>
'mw.doStuff( "bar" );',
2703 $links = $method->invokeArgs(
$out,
$args );
2704 $actualHtml = strval( $links );
2705 $this->assertEquals( $expectedHtml, $actualHtml );
2708 public static function provideMakeResourceLoaderLink() {
2714 "<script nonce=\"secret\">(window.RLQ=window.RLQ||[]).push(function(){"
2715 .
'mw.loader.load("http://127.0.0.1:8080/w/load.php?lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback");'
2722 '<link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback"/>'
2727 "<script nonce=\"secret\">(window.RLQ=window.RLQ||[]).push(function(){"
2728 .
"mw.test.baz({token:123});\nmw.loader.state({\"test.quux\":\"ready\"});"
2734 "<script nonce=\"secret\">(window.RLQ=window.RLQ||[]).push(function(){"
2735 .
"mw.loader.implement(\"test.quux@1ev0ijv\",function($,jQuery,require,module){"
2736 .
"mw.test.baz({token:123});},{\"css\":[\".mw-icon{transition:none}"
2737 .
"\"]});});</script>"
2747 '<noscript><link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?lang=en&modules=test.noscript&only=styles&skin=fallback"/></noscript>'
2752 "<script nonce=\"secret\">(window.RLQ=window.RLQ||[]).push(function(){"
2753 .
'mw.loader.load("http://127.0.0.1:8080/w/load.php?lang=en\u0026modules=test.group.bar\u0026skin=fallback");'
2754 .
'mw.loader.load("http://127.0.0.1:8080/w/load.php?lang=en\u0026modules=test.group.foo\u0026skin=fallback");'
2766 public function testBuildExemptModules(
array $exemptStyleModules, $expect ) {
2768 'wgResourceLoaderDebug' =>
false,
2769 'wgLoadScript' =>
'/w/load.php',
2772 'wgCacheEpoch' =>
'20140101000000',
2778 $ctx->setLanguage(
'en' );
2780 ->setConstructorArgs( [ $ctx ] )
2781 ->setMethods( [
'buildCssLinksArray' ] )
2783 $op->expects( $this->
any() )
2784 ->method(
'buildCssLinksArray' )
2786 $rl = $op->getResourceLoader();
2796 $op = TestingAccessWrapper::newFromObject( $op );
2797 $op->rlExemptStyleModules = $exemptStyleModules;
2798 $this->assertEquals(
2800 strval( $op->buildExemptModules() )
2804 public static function provideBuildExemptModules() {
2808 'exemptStyleModules' => [],
2809 '<meta name="ResourceLoaderDynamicStyles" content=""/>',
2812 'exemptStyleModules' => [
'site' => [],
'noscript' => [],
'private' => [],
'user' => [] ],
2813 '<meta name="ResourceLoaderDynamicStyles" content=""/>',
2815 'default logged-out' => [
2816 'exemptStyleModules' => [
'site' => [
'site.styles' ] ],
2817 '<meta name="ResourceLoaderDynamicStyles" content=""/>' .
"\n" .
2818 '<link rel="stylesheet" href="/w/load.php?lang=en&modules=site.styles&only=styles&skin=fallback"/>',
2820 'default logged-in' => [
2821 'exemptStyleModules' => [
'site' => [
'site.styles' ],
'user' => [
'user.styles' ] ],
2822 '<meta name="ResourceLoaderDynamicStyles" content=""/>' .
"\n" .
2823 '<link rel="stylesheet" href="/w/load.php?lang=en&modules=site.styles&only=styles&skin=fallback"/>' .
"\n" .
2824 '<link rel="stylesheet" href="/w/load.php?lang=en&modules=user.styles&only=styles&skin=fallback&version=1ai9g6t"/>',
2826 'custom modules' => [
2827 'exemptStyleModules' => [
2828 'site' => [
'site.styles',
'example.site.a',
'example.site.b' ],
2829 'user' => [
'user.styles',
'example.user' ],
2831 '<meta name="ResourceLoaderDynamicStyles" content=""/>' .
"\n" .
2832 '<link rel="stylesheet" href="/w/load.php?lang=en&modules=example.site.a%2Cb&only=styles&skin=fallback"/>' .
"\n" .
2833 '<link rel="stylesheet" href="/w/load.php?lang=en&modules=site.styles&only=styles&skin=fallback"/>' .
"\n" .
2834 '<link rel="stylesheet" href="/w/load.php?lang=en&modules=example.user&only=styles&skin=fallback&version=0a56zyi"/>' .
"\n" .
2835 '<link rel="stylesheet" href="/w/load.php?lang=en&modules=user.styles&only=styles&skin=fallback&version=1ai9g6t"/>',
2846 public function testTransformResourcePath( $baseDir,
$basePath, $uploadDir =
null,
2847 $uploadPath =
null,
$path =
null, $expected =
null
2849 if (
$path ===
null ) {
2852 $expected = $uploadPath;
2853 $uploadDir =
"$baseDir/images";
2854 $uploadPath =
"$basePath/images";
2859 'UploadDirectory' => $uploadDir,
2860 'UploadPath' => $uploadPath,
2864 Wikimedia\suppressWarnings();
2865 $actual = OutputPage::transformResourcePath( $conf,
$path );
2866 Wikimedia\restoreWarnings();
2868 $this->assertEquals( $expected ?:
$path, $actual );
2871 public static function provideTransformFilePath() {
2872 $baseDir = dirname( __DIR__ ) .
'/data/media';
2876 'baseDir' => $baseDir,
'basePath' =>
'/w',
2882 'baseDir' => $baseDir,
'basePath' =>
'/w',
2888 'baseDir' => $baseDir,
'basePath' =>
'/w',
2893 'baseDir' => $baseDir,
'basePath' =>
'/w',
2899 'baseDir' => $baseDir,
'basePath' =>
'/w',
2900 '//example.org/w/test.jpg'
2903 'baseDir' => $baseDir,
'basePath' =>
'/w',
2904 'https://example.org/w/test.jpg'
2908 'baseDir' => $baseDir,
'basePath' =>
'/w',
2909 'https://example.org/files/test.jpg'
2912 'baseDir' => $baseDir,
'basePath' =>
'/w',
2913 '//example.org/files/test.jpg'
2917 'baseDir' => $baseDir,
'basePath' =>
'',
2918 'https://example.org/files/test.jpg'
2921 'baseDir' => $baseDir,
'basePath' =>
'',
2923 '//example.org/files/test.jpg'
2927 'baseDir' => dirname( $baseDir ),
'basePath' =>
'',
2928 'uploadDir' => $baseDir,
'uploadPath' =>
'/images',
2930 '/images/test.jpg?edcf2'
2949 protected function assertTransformCssMediaCase(
$args ) {
2951 if ( isset(
$args[
'printableQuery'] ) ) {
2952 $queryData[
'printable'] =
$args[
'printableQuery'];
2955 if ( isset(
$args[
'handheldQuery'] ) ) {
2956 $queryData[
'handheld'] =
$args[
'handheldQuery'];
2959 $fauxRequest =
new FauxRequest( $queryData,
false );
2961 'wgRequest' => $fauxRequest,
2964 $actualReturn = OutputPage::transformCssMedia(
$args[
'media'] );
2965 $this->assertSame(
$args[
'expectedReturn'], $actualReturn,
$args[
'message'] );
2973 public function testPrintRequests() {
2974 $this->assertTransformCssMediaCase( [
2975 'printableQuery' =>
'1',
2976 'media' =>
'screen',
2977 'expectedReturn' =>
null,
2978 'message' =>
'On printable request, screen returns null'
2981 $this->assertTransformCssMediaCase( [
2982 'printableQuery' =>
'1',
2983 'media' => self::SCREEN_MEDIA_QUERY,
2984 'expectedReturn' =>
null,
2985 'message' =>
'On printable request, screen media query returns null'
2988 $this->assertTransformCssMediaCase( [
2989 'printableQuery' =>
'1',
2990 'media' => self::SCREEN_ONLY_MEDIA_QUERY,
2991 'expectedReturn' =>
null,
2992 'message' =>
'On printable request, screen media query with only returns null'
2995 $this->assertTransformCssMediaCase( [
2996 'printableQuery' =>
'1',
2998 'expectedReturn' =>
'',
2999 'message' =>
'On printable request, media print returns empty string'
3008 public function testScreenRequests() {
3009 $this->assertTransformCssMediaCase( [
3010 'media' =>
'screen',
3011 'expectedReturn' =>
'screen',
3012 'message' =>
'On screen request, screen media type is preserved'
3015 $this->assertTransformCssMediaCase( [
3016 'media' =>
'handheld',
3017 'expectedReturn' =>
'handheld',
3018 'message' =>
'On screen request, handheld media type is preserved'
3021 $this->assertTransformCssMediaCase( [
3022 'media' => self::SCREEN_MEDIA_QUERY,
3023 'expectedReturn' => self::SCREEN_MEDIA_QUERY,
3024 'message' =>
'On screen request, screen media query is preserved.'
3027 $this->assertTransformCssMediaCase( [
3028 'media' => self::SCREEN_ONLY_MEDIA_QUERY,
3029 'expectedReturn' => self::SCREEN_ONLY_MEDIA_QUERY,
3030 'message' =>
'On screen request, screen media query with only is preserved.'
3033 $this->assertTransformCssMediaCase( [
3035 'expectedReturn' =>
'print',
3036 'message' =>
'On screen request, print media type is preserved'
3045 public function testHandheld() {
3046 $this->assertTransformCssMediaCase( [
3047 'handheldQuery' =>
'1',
3048 'media' =>
'handheld',
3049 'expectedReturn' =>
'',
3050 'message' =>
'On request with handheld querystring and media is handheld, returns empty string'
3053 $this->assertTransformCssMediaCase( [
3054 'handheldQuery' =>
'1',
3055 'media' =>
'screen',
3056 'expectedReturn' =>
null,
3057 'message' =>
'On request with handheld querystring and media is screen, returns null'
3066 public function testIsTOCEnabled() {
3067 $op = $this->newInstance();
3068 $this->assertFalse( $op->isTOCEnabled() );
3070 $pOut1 = $this->createParserOutputStub(
'getTOCHTML',
false );
3071 $op->addParserOutputMetadata( $pOut1 );
3072 $this->assertFalse( $op->isTOCEnabled() );
3074 $pOut2 = $this->createParserOutputStub(
'getTOCHTML',
true );
3075 $op->addParserOutput( $pOut2 );
3076 $this->assertTrue( $op->isTOCEnabled() );
3079 $op->addParserOutputMetadata( $pOut1 );
3080 $this->assertTrue( $op->isTOCEnabled() );
3088 public function testPreloadLinkHeaders( $config,
$result ) {
3091 ->disableOriginalConstructor()->getMock();
3094 $this->assertEquals( [
$result ], $module->getHeaders( $ctx ) );
3097 public function providePreloadLinkHeaders() {
3101 'wgResourceBasePath' =>
'/w',
3102 'wgLogo' =>
'/img/default.png',
3104 '1.5x' =>
'/img/one-point-five.png',
3105 '2x' =>
'/img/two-x.png',
3108 'Link: </img/default.png>;rel=preload;as=image;media=' .
3109 'not all and (min-resolution: 1.5dppx),' .
3110 '</img/one-point-five.png>;rel=preload;as=image;media=' .
3111 '(min-resolution: 1.5dppx) and (max-resolution: 1.999999dppx),' .
3112 '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
3116 'wgResourceBasePath' =>
'/w',
3117 'wgLogo' =>
'/img/default.png',
3118 'wgLogoHD' =>
false,
3120 'Link: </img/default.png>;rel=preload;as=image'
3124 'wgResourceBasePath' =>
'/w',
3125 'wgLogo' =>
'/img/default.png',
3127 '2x' =>
'/img/two-x.png',
3130 'Link: </img/default.png>;rel=preload;as=image;media=' .
3131 'not all and (min-resolution: 2dppx),' .
3132 '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
3136 'wgResourceBasePath' =>
'/w',
3137 'wgLogo' =>
'/img/default.png',
3139 'svg' =>
'/img/vector.svg',
3142 'Link: </img/vector.svg>;rel=preload;as=image'
3147 'wgResourceBasePath' =>
'/w',
3148 'wgLogo' =>
'/w/test.jpg',
3149 'wgLogoHD' =>
false,
3150 'wgUploadPath' =>
'/w/images',
3151 'IP' => dirname( __DIR__ ) .
'/data/media',
3153 'Link: </w/test.jpg?edcf2>;rel=preload;as=image',
3166 'AppleTouchIcon' =>
false,
3167 'DisableLangConversion' =>
true,
3168 'EnableCanonicalServerLink' =>
false,
3171 'LanguageCode' =>
false,
3172 'ReferrerPolicy' =>
false,
3173 'RightsPage' =>
false,
3174 'RightsUrl' =>
false,
3175 'UniversalEditButton' =>
false,