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 protected function setUp() {
18 ResourceLoader::clearCache();
22 ResourceLoader::clearCache();
32 public function testRedirect( $url,
$code =
null ) {
33 $op = $this->newInstance();
34 if ( isset(
$code ) ) {
35 $op->redirect( $url,
$code );
37 $op->redirect( $url );
39 $expectedUrl = str_replace(
"\n",
'', $url );
40 $this->assertSame( $expectedUrl, $op->getRedirect() );
41 $this->assertSame( $expectedUrl, $op->mRedirect );
42 $this->assertSame(
$code ??
'302', $op->mRedirectCode );
45 public function provideRedirect() {
47 [
'http://example.com' ],
48 [
'http://example.com',
'400' ],
49 [
'http://example.com',
'squirrels!!!' ],
58 public function testSetCopyrightUrl() {
59 $op = $this->newInstance();
60 $op->setCopyrightUrl(
'http://example.com' );
63 Html::element(
'link', [
'rel' =>
'license',
'href' =>
'http://example.com' ] ),
64 $op->getHeadLinksArray()[
'copyright']
75 public function testMetaTags() {
76 $op = $this->newInstance();
77 $op->addMeta(
'http:expires',
'0' );
78 $op->addMeta(
'keywords',
'first' );
79 $op->addMeta(
'keywords',
'second' );
80 $op->addMeta(
'og:title',
'Ta-duh' );
83 [
'http:expires',
'0' ],
84 [
'keywords',
'first' ],
85 [
'keywords',
'second' ],
86 [
'og:title',
'Ta-duh' ],
88 $this->assertSame( $expected, $op->getMetaTags() );
90 $links = $op->getHeadLinksArray();
91 $this->assertContains(
'<meta http-equiv="expires" content="0"/>', $links );
92 $this->assertContains(
'<meta name="keywords" content="first"/>', $links );
93 $this->assertContains(
'<meta name="keywords" content="second"/>', $links );
94 $this->assertContains(
'<meta property="og:title" content="Ta-duh"/>', $links );
95 $this->assertArrayNotHasKey(
'meta-robots', $links );
103 public function testAddLink() {
104 $op = $this->newInstance();
108 [
'rel' =>
'foo',
'href' =>
'http://example.com' ],
112 $op->addLink(
$link );
115 $this->assertSame( $links, $op->getLinkTags() );
117 $result = $op->getHeadLinksArray();
129 public function testSetCanonicalUrl() {
130 $op = $this->newInstance();
131 $op->setCanonicalUrl(
'http://example.comm' );
132 $op->setCanonicalUrl(
'http://example.com' );
134 $this->assertSame(
'http://example.com', $op->getCanonicalUrl() );
136 $headLinks = $op->getHeadLinksArray();
139 'rel' =>
'canonical',
'href' =>
'http://example.com'
143 'rel' =>
'canonical',
'href' =>
'http://example.comm'
150 public function testAddScript() {
151 $op = $this->newInstance();
152 $op->addScript(
'some random string' );
154 $this->assertContains(
"\nsome random string\n",
"\n" . $op->getBottomScripts() .
"\n" );
160 public function testAddScriptFile() {
161 $op = $this->newInstance();
162 $op->addScriptFile(
'/somescript.js' );
163 $op->addScriptFile(
'//example.com/somescript.js' );
165 $this->assertContains(
168 "\n" . $op->getBottomScripts() .
"\n"
177 public function testAddDeprecatedScriptFileWarning() {
179 'Use of OutputPage::addScriptFile was deprecated in MediaWiki 1.24.' );
181 $op = $this->newInstance();
182 $op->addScriptFile(
'ignored-script.js' );
191 public function testAddDeprecatedScriptFileNoOp() {
193 $op = $this->newInstance();
194 $op->addScriptFile(
'ignored-script.js' );
196 $this->assertNotContains(
'ignored-script.js',
'' . $op->getBottomScripts() );
202 public function testAddInlineScript() {
203 $op = $this->newInstance();
204 $op->addInlineScript(
'let foo = "bar";' );
205 $op->addInlineScript(
'alert( foo );' );
207 $this->assertContains(
210 "\n" . $op->getBottomScripts() .
"\n"
220 public function testSetTarget() {
221 $op = $this->newInstance();
222 $op->setTarget(
'foo' );
224 $this->assertSame(
'foo', $op->getTarget() );
236 public function testHeadItems() {
237 $op = $this->newInstance();
238 $op->addHeadItem(
'a',
'b' );
239 $op->addHeadItems( [
'c' =>
'<d>&',
'e' =>
'f',
'a' =>
'q' ] );
240 $op->addHeadItem(
'e',
'g' );
241 $op->addHeadItems(
'x' );
243 $this->assertSame( [
'a' =>
'q',
'c' =>
'<d>&',
'e' =>
'g',
'x' ],
244 $op->getHeadItemsArray() );
246 $this->assertTrue( $op->hasHeadItem(
'a' ) );
247 $this->assertTrue( $op->hasHeadItem(
'c' ) );
248 $this->assertTrue( $op->hasHeadItem(
'e' ) );
249 $this->assertTrue( $op->hasHeadItem(
'0' ) );
251 $this->assertContains(
"\nq\n<d>&\ng\nx\n",
252 '' . $op->headElement( $op->getContext()->getSkin() ) );
260 public function testHeadItemsParserOutput() {
261 $op = $this->newInstance();
262 $stubPO1 = $this->createParserOutputStub(
'getHeadItems', [
'a' =>
'b' ] );
263 $op->addParserOutputMetadata( $stubPO1 );
264 $stubPO2 = $this->createParserOutputStub(
'getHeadItems',
265 [
'c' =>
'<d>&',
'e' =>
'f',
'a' =>
'q' ] );
266 $op->addParserOutputMetadata( $stubPO2 );
267 $stubPO3 = $this->createParserOutputStub(
'getHeadItems', [
'e' =>
'g' ] );
268 $op->addParserOutput( $stubPO3 );
269 $stubPO4 = $this->createParserOutputStub(
'getHeadItems', [
'x' ] );
270 $op->addParserOutputMetadata( $stubPO4 );
272 $this->assertSame( [
'a' =>
'q',
'c' =>
'<d>&',
'e' =>
'g',
'x' ],
273 $op->getHeadItemsArray() );
275 $this->assertTrue( $op->hasHeadItem(
'a' ) );
276 $this->assertTrue( $op->hasHeadItem(
'c' ) );
277 $this->assertTrue( $op->hasHeadItem(
'e' ) );
278 $this->assertTrue( $op->hasHeadItem(
'0' ) );
279 $this->assertFalse( $op->hasHeadItem(
'b' ) );
281 $this->assertContains(
"\nq\n<d>&\ng\nx\n",
282 '' . $op->headElement( $op->getContext()->getSkin() ) );
288 public function testAddBodyClasses() {
289 $op = $this->newInstance();
290 $op->addBodyClasses(
'a' );
291 $op->addBodyClasses(
'mediawiki' );
292 $op->addBodyClasses(
'b c' );
293 $op->addBodyClasses( [
'd',
'e' ] );
294 $op->addBodyClasses(
'a' );
296 $this->assertContains(
'"a mediawiki b c d e ltr',
297 '' . $op->headElement( $op->getContext()->getSkin() ) );
304 public function testArticleBodyOnly() {
305 $op = $this->newInstance();
306 $this->assertFalse( $op->getArticleBodyOnly() );
308 $op->setArticleBodyOnly(
true );
309 $this->assertTrue( $op->getArticleBodyOnly() );
311 $op->addHTML(
'<b>a</b>' );
313 $this->assertSame(
'<b>a</b>', $op->output(
true ) );
320 public function testProperties() {
321 $op = $this->newInstance();
323 $this->assertNull( $op->getProperty(
'foo' ) );
325 $op->setProperty(
'foo',
'bar' );
326 $op->setProperty(
'baz',
'quz' );
328 $this->assertSame(
'bar', $op->getProperty(
'foo' ) );
329 $this->assertSame(
'quz', $op->getProperty(
'baz' ) );
338 public function testCheckLastModified(
339 $timestamp, $ifModifiedSince, $expected, $config = [], $callback =
null
342 if ( $ifModifiedSince ) {
343 if ( is_numeric( $ifModifiedSince ) ) {
345 $ifModifiedSince = date(
'D, d M Y H:i:s', $ifModifiedSince ) .
' GMT';
347 $request->setHeader(
'If-Modified-Since', $ifModifiedSince );
350 if ( !isset( $config[
'CacheEpoch'] ) ) {
352 $config[
'CacheEpoch'] =
'20000101000000';
355 $op = $this->newInstance( $config,
$request );
358 $callback( $op, $this );
362 Wikimedia\suppressWarnings();
364 $this->assertEquals( $expected, $op->checkLastModified( $timestamp ) );
366 Wikimedia\restoreWarnings();
370 public function provideCheckLastModified() {
371 $lastModified = time() - 3600;
374 [
'0', $lastModified,
false ],
375 'Timestamp Unix epoch' =>
376 [
'19700101000000', $lastModified,
false ],
377 'Timestamp same as If-Modified-Since' =>
378 [ $lastModified, $lastModified,
true ],
379 'Timestamp one second after If-Modified-Since' =>
380 [ $lastModified + 1, $lastModified,
false ],
381 'No If-Modified-Since' =>
382 [ $lastModified + 1,
null,
false ],
383 'Malformed If-Modified-Since' =>
384 [ $lastModified + 1,
'GIBBERING WOMBATS !!!',
false ],
385 'Non-standard IE-style If-Modified-Since' =>
386 [ $lastModified, date(
'D, d M Y H:i:s', $lastModified ) .
' GMT; length=5202',
389 'If-Modified-Since not per spec but we accept it anyway because strtotime does' =>
390 [ $lastModified,
"@$lastModified",
true ],
391 '$wgCachePages = false' =>
392 [ $lastModified, $lastModified,
false, [
'CachePages' =>
false ] ],
394 [ $lastModified, $lastModified,
false,
395 [
'CacheEpoch' =>
wfTimestamp( TS_MW, $lastModified + 1 ) ] ],
396 'Recently-touched user' =>
397 [ $lastModified, $lastModified,
false, [],
399 $op->getContext()->setUser( $this->
getTestUser()->getUser() );
401 'After Squid expiry' =>
402 [ $lastModified, $lastModified,
false,
403 [
'UseSquid' =>
true,
'SquidMaxage' => 3599 ] ],
404 'Hook allows cache use' =>
405 [ $lastModified + 1, $lastModified,
true, [],
406 function ( $op, $that ) {
407 $that->setTemporaryHook(
'OutputPageCheckLastModified',
408 function ( &$modifiedTimes ) {
409 $modifiedTimes = [ 1 ];
413 'Hooks prohibits cache use' =>
414 [ $lastModified, $lastModified,
false, [],
415 function ( $op, $that ) {
416 $that->setTemporaryHook(
'OutputPageCheckLastModified',
417 function ( &$modifiedTimes ) {
418 $modifiedTimes = [ max( $modifiedTimes ) + 1 ];
430 public function testCdnCacheEpoch(
$params ) {
431 $out = TestingAccessWrapper::newFromObject( $this->newInstance() );
432 $reqTime = strtotime(
$params[
'reqTime'] );
433 $pageTime = strtotime(
$params[
'pageTime'] );
434 $actual = max( $pageTime,
$out->getCdnCacheEpoch( $reqTime,
$params[
'maxAge'] ) );
438 gmdate( DateTime::ATOM, $actual ),
443 public static function provideCdnCacheEpoch() {
445 'pageTime' =>
'2011-04-01T12:00:00+00:00',
446 'maxAge' => 24 * 3600,
449 'after 1s' => [
$base + [
450 'reqTime' =>
'2011-04-01T12:00:01+00:00',
451 'expect' =>
'2011-04-01T12:00:00+00:00',
453 'after 23h' => [
$base + [
454 'reqTime' =>
'2011-04-02T11:00:00+00:00',
455 'expect' =>
'2011-04-01T12:00:00+00:00',
457 'after 24h and a bit' => [
$base + [
458 'reqTime' =>
'2011-04-02T12:34:56+00:00',
459 'expect' =>
'2011-04-01T12:34:56+00:00',
461 'after a year' => [
$base + [
462 'reqTime' =>
'2012-05-06T00:12:07+00:00',
463 'expect' =>
'2012-05-05T00:12:07+00:00',
474 public function testSetRobotPolicy() {
475 $op = $this->newInstance();
476 $op->setRobotPolicy(
'noindex, nofollow' );
478 $links = $op->getHeadLinksArray();
479 $this->assertContains(
'<meta name="robots" content="noindex,nofollow"/>', $links );
487 public function testSetIndexFollowPolicies() {
488 $op = $this->newInstance();
489 $op->setIndexPolicy(
'noindex' );
490 $op->setFollowPolicy(
'nofollow' );
492 $links = $op->getHeadLinksArray();
493 $this->assertContains(
'<meta name="robots" content="noindex,nofollow"/>', $links );
496 private function extractHTMLTitle( OutputPage $op ) {
497 $html = $op->headElement( $op->getContext()->getSkin() );
503 $this->assertLessThanOrEqual( 1,
count(
$matches[1] ),
'More than one <title>!' );
515 private static function getMsgText( $op, ...$msgParams ) {
516 return $op->msg( ...$msgParams )->inContentLanguage()->text();
523 public function testHTMLTitle() {
524 $op = $this->newInstance();
527 $this->assertSame(
'', $op->getHTMLTitle() );
528 $this->assertSame(
'', $op->getPageTitle() );
530 $this->getMsgText( $op,
'pagetitle',
'' ),
531 $this->extractHTMLTitle( $op )
535 $op->setHTMLTitle(
'Potatoes will eat me' );
537 $this->assertSame(
'Potatoes will eat me', $op->getHTMLTitle() );
538 $this->assertSame(
'Potatoes will eat me', $this->extractHTMLTitle( $op ) );
540 $this->assertSame(
'', $op->getPageTitle() );
543 $msg = $op->msg(
'mainpage' );
545 $op->setHTMLTitle( $msg );
546 $this->assertSame( $msg->text(), $op->getHTMLTitle() );
547 $this->assertSame( $msg->text(), $this->extractHTMLTitle( $op ) );
548 $this->assertSame(
'', $op->getPageTitle() );
554 public function testSetRedirectedFrom() {
555 $op = $this->newInstance();
558 $this->assertSame(
'Talk:Some_page', $op->getJSVars()[
'wgRedirectedFrom'] );
565 public function testPageTitle() {
567 $op = $this->newInstance();
570 $this->assertSame(
'', $op->getPageTitle() );
571 $this->assertSame(
'', $op->getHTMLTitle() );
574 $op->setPageTitle(
'foobar' );
576 $this->assertSame(
'foobar', $op->getPageTitle() );
578 $this->assertSame( $this->getMsgText( $op,
'pagetitle',
'foobar' ), $op->getHTMLTitle() );
582 $op->setPageTitle(
'<script>a</script>&<i>b</i>' );
584 $this->assertSame(
'<script>a</script>&<i>b</i>', $op->getPageTitle() );
586 $this->getMsgText( $op,
'pagetitle',
'<script>a</script>&b' ),
591 $text = $this->getMsgText( $op,
'mainpage' );
593 $op->setPageTitle( $op->msg(
'mainpage' )->inContentLanguage() );
594 $this->assertSame( $text, $op->getPageTitle() );
595 $this->assertSame( $this->getMsgText( $op,
'pagetitle', $text ), $op->getHTMLTitle() );
601 public function testSetTitle() {
602 $op = $this->newInstance();
604 $this->assertSame(
'My test page', $op->getTitle()->getPrefixedText() );
608 $this->assertSame(
'Another test page', $op->getTitle()->getPrefixedText() );
617 public function testSubtitle() {
618 $op = $this->newInstance();
620 $this->assertSame(
'', $op->getSubtitle() );
622 $op->addSubtitle(
'<b>foo</b>' );
624 $this->assertSame(
'<b>foo</b>', $op->getSubtitle() );
626 $op->addSubtitle( $op->msg(
'mainpage' )->inContentLanguage() );
629 "<b>foo</b><br />\n\t\t\t\t" . $this->getMsgText( $op,
'mainpage' ),
633 $op->setSubtitle(
'There can be only one' );
635 $this->assertSame(
'There can be only one', $op->getSubtitle() );
637 $op->clearSubtitle();
639 $this->assertSame(
'', $op->getSubtitle() );
647 public function testBuildBacklinkSubtitle(
$titles,
$queries, $contains, $notContains ) {
650 $this->assertTrue(
true );
658 $this->
editPage(
'Page 2',
'#REDIRECT [[Page 1]]' );
660 $str = OutputPage::buildBacklinkSubtitle(
$title,
$query )->text();
662 foreach ( $contains
as $substr ) {
663 $this->assertContains( $substr, $str );
666 foreach ( $notContains
as $substr ) {
667 $this->assertNotContains( $substr, $str );
677 public function testAddBacklinkSubtitle(
$titles,
$queries, $contains, $notContains ) {
679 $this->
editPage(
'Page 2',
'#REDIRECT [[Page 1]]' );
681 $op = $this->newInstance();
686 $str = $op->getSubtitle();
688 foreach ( $contains
as $substr ) {
689 $this->assertContains( $substr, $str );
692 foreach ( $notContains
as $substr ) {
693 $this->assertNotContains( $substr, $str );
697 public function provideBacklinkSubtitle() {
703 [
'redirect',
'Page 2' ],
713 [ [
'action' =>
'edit' ] ],
718 [
'Page 1',
'Page 2' ],
720 [
'Page 1',
'Page 2',
"<br />\n\t\t\t\t" ],
731 public function testPrintable() {
732 $op = $this->newInstance();
734 $this->assertFalse( $op->isPrintable() );
738 $this->assertTrue( $op->isPrintable() );
745 public function testDisable() {
746 $op = $this->newInstance();
748 $this->assertFalse( $op->isDisabled() );
749 $this->assertNotSame(
'', $op->output(
true ) );
753 $this->assertTrue( $op->isDisabled() );
754 $this->assertSame(
'', $op->output(
true ) );
762 public function testShowNewSectionLink() {
763 $op = $this->newInstance();
765 $this->assertFalse( $op->showNewSectionLink() );
767 $pOut1 = $this->createParserOutputStub(
'getNewSection',
true );
768 $op->addParserOutputMetadata( $pOut1 );
769 $this->assertTrue( $op->showNewSectionLink() );
771 $pOut2 = $this->createParserOutputStub(
'getNewSection',
false );
772 $op->addParserOutput( $pOut2 );
773 $this->assertFalse( $op->showNewSectionLink() );
781 public function testForceHideNewSectionLink() {
782 $op = $this->newInstance();
784 $this->assertFalse( $op->forceHideNewSectionLink() );
786 $pOut1 = $this->createParserOutputStub(
'getHideNewSection',
true );
787 $op->addParserOutputMetadata( $pOut1 );
788 $this->assertTrue( $op->forceHideNewSectionLink() );
790 $pOut2 = $this->createParserOutputStub(
'getHideNewSection',
false );
791 $op->addParserOutput( $pOut2 );
792 $this->assertFalse( $op->forceHideNewSectionLink() );
799 public function testSetSyndicated() {
800 $op = $this->newInstance();
801 $this->assertFalse( $op->isSyndicated() );
803 $op->setSyndicated();
804 $this->assertTrue( $op->isSyndicated() );
806 $op->setSyndicated(
false );
807 $this->assertFalse( $op->isSyndicated() );
816 public function testFeedLinks() {
817 $op = $this->newInstance();
818 $this->assertSame( [], $op->getSyndicationLinks() );
820 $op->addFeedLink(
'not a supported format',
'abc' );
821 $this->assertFalse( $op->isSyndicated() );
822 $this->assertSame( [], $op->getSyndicationLinks() );
824 $feedTypes = $op->getConfig()->get(
'AdvertisedFeedTypes' );
826 $op->addFeedLink( $feedTypes[0],
'def' );
827 $this->assertTrue( $op->isSyndicated() );
828 $this->assertSame( [ $feedTypes[0] =>
'def' ], $op->getSyndicationLinks() );
830 $op->setFeedAppendQuery(
false );
832 foreach ( $feedTypes
as $type ) {
833 $expected[
$type] = $op->getTitle()->getLocalURL(
"feed=$type" );
835 $this->assertSame( $expected, $op->getSyndicationLinks() );
837 $op->setFeedAppendQuery(
'apples=oranges' );
838 foreach ( $feedTypes
as $type ) {
839 $expected[
$type] = $op->getTitle()->getLocalURL(
"feed=$type&apples=oranges" );
841 $this->assertSame( $expected, $op->getSyndicationLinks() );
850 function testArticleFlags() {
851 $op = $this->newInstance();
852 $this->assertFalse( $op->isArticle() );
853 $this->assertTrue( $op->isArticleRelated() );
855 $op->setArticleRelated(
false );
856 $this->assertFalse( $op->isArticle() );
857 $this->assertFalse( $op->isArticleRelated() );
859 $op->setArticleFlag(
true );
860 $this->assertTrue( $op->isArticle() );
861 $this->assertTrue( $op->isArticleRelated() );
863 $op->setArticleFlag(
false );
864 $this->assertFalse( $op->isArticle() );
865 $this->assertTrue( $op->isArticleRelated() );
867 $op->setArticleFlag(
true );
868 $op->setArticleRelated(
false );
869 $this->assertFalse( $op->isArticle() );
870 $this->assertFalse( $op->isArticleRelated() );
880 function testLanguageLinks() {
881 $op = $this->newInstance();
882 $this->assertSame( [], $op->getLanguageLinks() );
884 $op->addLanguageLinks( [
'fr:A',
'it:B' ] );
885 $this->assertSame( [
'fr:A',
'it:B' ], $op->getLanguageLinks() );
887 $op->addLanguageLinks( [
'de:C',
'es:D' ] );
888 $this->assertSame( [
'fr:A',
'it:B',
'de:C',
'es:D' ], $op->getLanguageLinks() );
890 $op->setLanguageLinks( [
'pt:E' ] );
891 $this->assertSame( [
'pt:E' ], $op->getLanguageLinks() );
893 $pOut1 = $this->createParserOutputStub(
'getLanguageLinks', [
'he:F',
'ar:G' ] );
894 $op->addParserOutputMetadata( $pOut1 );
895 $this->assertSame( [
'pt:E',
'he:F',
'ar:G' ], $op->getLanguageLinks() );
897 $pOut2 = $this->createParserOutputStub(
'getLanguageLinks', [
'pt:H' ] );
898 $op->addParserOutput( $pOut2 );
899 $this->assertSame( [
'pt:E',
'he:F',
'ar:G',
'pt:H' ], $op->getLanguageLinks() );
919 public function testAddCategoryLinks(
923 $expectedNormal = $this->extractExpectedCategories( $expectedNormal,
'add' );
924 $expectedHidden = $this->extractExpectedCategories( $expectedHidden,
'add' );
926 $op = $this->setupCategoryTests( $fakeResults, $variantLinkCallback );
928 $op->addCategoryLinks(
$args );
930 $this->doCategoryAsserts( $op, $expectedNormal, $expectedHidden );
931 $this->doCategoryLinkAsserts( $op, $expectedNormal, $expectedHidden );
941 public function testAddCategoryLinksOneByOne(
947 $this->assertTrue(
true );
951 $expectedNormal = $this->extractExpectedCategories( $expectedNormal,
'onebyone' );
952 $expectedHidden = $this->extractExpectedCategories( $expectedHidden,
'onebyone' );
954 $op = $this->setupCategoryTests( $fakeResults, $variantLinkCallback );
956 foreach (
$args as $key => $val ) {
957 $op->addCategoryLinks( [ $key => $val ] );
960 $this->doCategoryAsserts( $op, $expectedNormal, $expectedHidden );
961 $this->doCategoryLinkAsserts( $op, $expectedNormal, $expectedHidden );
971 public function testSetCategoryLinks(
975 $expectedNormal = $this->extractExpectedCategories( $expectedNormal,
'set' );
976 $expectedHidden = $this->extractExpectedCategories( $expectedHidden,
'set' );
978 $op = $this->setupCategoryTests( $fakeResults, $variantLinkCallback );
980 $op->setCategoryLinks( [
'Initial page' =>
'Initial page' ] );
981 $op->setCategoryLinks(
$args );
984 $expectedNormalCats = array_merge( [
'Initial page' ], $expectedNormal );
985 $expectedCats = array_merge( $expectedHidden, $expectedNormalCats );
987 $this->doCategoryAsserts( $op, $expectedNormalCats, $expectedHidden );
988 $this->doCategoryLinkAsserts( $op, $expectedNormal, $expectedHidden );
999 public function testParserOutputCategoryLinks(
1000 array $args,
array $fakeResults, callable $variantLinkCallback =
null,
1003 $expectedNormal = $this->extractExpectedCategories( $expectedNormal,
'pout' );
1004 $expectedHidden = $this->extractExpectedCategories( $expectedHidden,
'pout' );
1006 $op = $this->setupCategoryTests( $fakeResults, $variantLinkCallback );
1008 $stubPO = $this->createParserOutputStub(
'getCategories',
$args );
1014 $method = [
'addParserOutputMetadata',
'addParserOutput' ][$idx % 2];
1015 $op->$method( $stubPO );
1017 $this->doCategoryAsserts( $op, $expectedNormal, $expectedHidden );
1018 $this->doCategoryLinkAsserts( $op, $expectedNormal, $expectedHidden );
1026 private function extractExpectedCategories(
array $expected, $key ) {
1027 if ( !$expected || isset( $expected[0] ) ) {
1030 return $expected[$key] ?? $expected[
'default'];
1033 private function setupCategoryTests(
1034 array $fakeResults, callable $variantLinkCallback =
null
1040 ->setMethods( [
'addCategoryLinksToLBAndGetResult',
'getTitle' ] )
1044 $op->expects( $this->
any() )
1045 ->method(
'getTitle' )
1046 ->will( $this->returnValue(
$title ) );
1048 $op->expects( $this->
any() )
1049 ->method(
'addCategoryLinksToLBAndGetResult' )
1050 ->will( $this->returnCallback(
function (
array $categories )
use ( $fakeResults ) {
1052 foreach ( $categories
as $category => $unused ) {
1053 if ( isset( $fakeResults[$category] ) ) {
1054 $return[] = $fakeResults[$category];
1057 return new FakeResultWrapper( $return );
1060 if ( $variantLinkCallback ) {
1062 ->setConstructorArgs( [
'en' ] )
1063 ->setMethods( [
'findVariantLink' ] )
1065 $mockContLang->expects( $this->
any() )
1066 ->method(
'findVariantLink' )
1067 ->will( $this->returnCallback( $variantLinkCallback ) );
1071 $this->assertSame( [], $op->getCategories() );
1076 private function doCategoryAsserts( $op, $expectedNormal, $expectedHidden ) {
1077 $this->assertSame( array_merge( $expectedHidden, $expectedNormal ), $op->getCategories() );
1078 $this->assertSame( $expectedNormal, $op->getCategories(
'normal' ) );
1079 $this->assertSame( $expectedHidden, $op->getCategories(
'hidden' ) );
1082 private function doCategoryLinkAsserts( $op, $expectedNormal, $expectedHidden ) {
1083 $catLinks = $op->getCategoryLinks();
1084 $this->assertSame( (
bool)$expectedNormal + (
bool)$expectedHidden,
count( $catLinks ) );
1085 if ( $expectedNormal ) {
1086 $this->assertSame(
count( $expectedNormal ),
count( $catLinks[
'normal'] ) );
1088 if ( $expectedHidden ) {
1089 $this->assertSame(
count( $expectedHidden ),
count( $catLinks[
'hidden'] ) );
1092 foreach ( $expectedNormal
as $i =>
$name ) {
1093 $this->assertContains(
$name, $catLinks[
'normal'][$i] );
1095 foreach ( $expectedHidden
as $i =>
$name ) {
1096 $this->assertContains(
$name, $catLinks[
'hidden'][$i] );
1100 public function provideGetCategories() {
1102 'No categories' => [ [], [],
null, [], [] ],
1104 [
'Test1' =>
'Some sortkey',
'Test2' =>
'A different sortkey' ],
1105 [
'Test1' => (
object)[
'pp_value' => 1,
'page_title' =>
'Test1' ],
1106 'Test2' => (
object)[
'page_title' =>
'Test2' ] ],
1111 'Invalid title' => [
1112 [
'[' =>
'[',
'Test' =>
'Test' ],
1113 [
'Test' => (
object)[
'page_title' =>
'Test' ] ],
1119 [
'Test' =>
'Test',
'Estay' =>
'Estay' ],
1120 [
'Test' => (
object)[
'page_title' =>
'Test' ] ],
1122 if (
$link ===
'Estay' ) {
1129 [
'onebyone' => [
'Test',
'Test' ],
'default' => [
'Test' ] ],
1138 public function testGetCategoriesInvalid() {
1140 'Invalid category type given: hiddne' );
1142 $op = $this->newInstance();
1143 $op->getCategories(
'hiddne' );
1155 public function testIndicators() {
1156 $op = $this->newInstance();
1157 $this->assertSame( [], $op->getIndicators() );
1159 $op->setIndicators( [] );
1160 $this->assertSame( [], $op->getIndicators() );
1163 $op->setIndicators( [
'b' =>
'x',
'a' =>
'y' ] );
1164 $this->assertSame( [
'a' =>
'y',
'b' =>
'x' ], $op->getIndicators() );
1167 $op->setIndicators( [
'c' =>
'z',
'a' =>
'w' ] );
1168 $this->assertSame( [
'a' =>
'w',
'b' =>
'x',
'c' =>
'z' ], $op->getIndicators() );
1171 $pOut1 = $this->createParserOutputStub(
'getIndicators', [
'c' =>
'u',
'd' =>
'v' ] );
1172 $op->addParserOutputMetadata( $pOut1 );
1173 $this->assertSame( [
'a' =>
'w',
'b' =>
'x',
'c' =>
'u',
'd' =>
'v' ],
1174 $op->getIndicators() );
1177 $pOut2 = $this->createParserOutputStub(
'getIndicators', [
'a' =>
'!!!' ] );
1178 $op->addParserOutput( $pOut2 );
1179 $this->assertSame( [
'a' =>
'!!!',
'b' =>
'x',
'c' =>
'u',
'd' =>
'v' ],
1180 $op->getIndicators() );
1187 public function testAddHelpLink() {
1188 $op = $this->newInstance();
1190 $op->addHelpLink(
'Manual:PHP unit testing' );
1191 $indicators = $op->getIndicators();
1192 $this->assertSame( [
'mw-helplink' ], array_keys( $indicators ) );
1193 $this->assertContains(
'Manual:PHP_unit_testing', $indicators[
'mw-helplink'] );
1195 $op->addHelpLink(
'https://phpunit.de',
true );
1196 $indicators = $op->getIndicators();
1197 $this->assertSame( [
'mw-helplink' ], array_keys( $indicators ) );
1198 $this->assertContains(
'https://phpunit.de', $indicators[
'mw-helplink'] );
1199 $this->assertNotContains(
'mediawiki', $indicators[
'mw-helplink'] );
1200 $this->assertNotContains(
'Manual:PHP', $indicators[
'mw-helplink'] );
1210 public function testBodyHTML() {
1211 $op = $this->newInstance();
1212 $this->assertSame(
'', $op->getHTML() );
1214 $op->addHTML(
'a' );
1215 $this->assertSame(
'a', $op->getHTML() );
1217 $op->addHTML(
'b' );
1218 $this->assertSame(
'ab', $op->getHTML() );
1220 $op->prependHTML(
'c' );
1221 $this->assertSame(
'cab', $op->getHTML() );
1223 $op->addElement(
'p', [
'id' =>
'foo' ],
'd' );
1224 $this->assertSame(
'cab<p id="foo">d</p>', $op->getHTML() );
1227 $this->assertSame(
'', $op->getHTML() );
1235 public function testRevisionId( $newVal, $expected ) {
1236 $op = $this->newInstance();
1238 $this->assertNull( $op->setRevisionId( $newVal ) );
1239 $this->assertSame( $expected, $op->getRevisionId() );
1240 $this->assertSame( $expected, $op->setRevisionId(
null ) );
1241 $this->assertNull( $op->getRevisionId() );
1244 public function provideRevisionId() {
1251 [
'32% finished', 32 ],
1260 public function testRevisionTimestamp() {
1261 $op = $this->newInstance();
1262 $this->assertNull( $op->getRevisionTimestamp() );
1264 $this->assertNull( $op->setRevisionTimestamp(
'abc' ) );
1265 $this->assertSame(
'abc', $op->getRevisionTimestamp() );
1266 $this->assertSame(
'abc', $op->setRevisionTimestamp(
null ) );
1267 $this->assertNull( $op->getRevisionTimestamp() );
1274 public function testFileVersion() {
1275 $op = $this->newInstance();
1276 $this->assertNull( $op->getFileVersion() );
1279 $stubFile->method(
'exists' )->willReturn(
true );
1280 $stubFile->method(
'getTimestamp' )->willReturn(
'12211221123321' );
1281 $stubFile->method(
'getSha1' )->willReturn(
'bf3ffa7047dc080f5855377a4f83cd18887e3b05' );
1283 $op->setFileVersion( $stubFile );
1285 $this->assertEquals(
1286 [
'time' =>
'12211221123321',
'sha1' =>
'bf3ffa7047dc080f5855377a4f83cd18887e3b05' ],
1287 $op->getFileVersion()
1290 $stubMissingFile = $this->createMock(
File::class );
1291 $stubMissingFile->method(
'exists' )->willReturn(
false );
1293 $op->setFileVersion( $stubMissingFile );
1294 $this->assertNull( $op->getFileVersion() );
1296 $op->setFileVersion( $stubFile );
1297 $this->assertNotNull( $op->getFileVersion() );
1299 $op->setFileVersion(
null );
1300 $this->assertNull( $op->getFileVersion() );
1307 private function createParserOutputStub( ...
$args ) {
1311 $retVals =
$args[0];
1316 foreach ( $retVals
as $method => $retVal ) {
1317 $pOut->method( $method )->willReturn( $retVal );
1320 $arrayReturningMethods = [
1322 'getFileSearchOptions',
1330 foreach ( $arrayReturningMethods
as $method ) {
1331 $pOut->method( $method )->willReturn( [] );
1342 public function testTemplateIds() {
1343 $op = $this->newInstance();
1344 $this->assertSame( [], $op->getTemplateIds() );
1347 $stubPOEmpty = $this->createParserOutputStub();
1348 $op->addParserOutputMetadata( $stubPOEmpty );
1349 $this->assertSame( [], $op->getTemplateIds() );
1353 NS_MAIN => [
'A' => 3,
'B' => 17 ],
1358 $stubPO1 = $this->createParserOutputStub(
'getTemplateIds', $ids );
1360 $op->addParserOutputMetadata( $stubPO1 );
1361 $this->assertSame( $ids, $op->getTemplateIds() );
1364 $stubPO2 = $this->createParserOutputStub(
'getTemplateIds', [
1370 NS_MAIN => [
'E' => 1234,
'A' => 3,
'B' => 17 ],
1376 $op->addParserOutput( $stubPO2 );
1377 $this->assertSame( $finalIds, $op->getTemplateIds() );
1380 $op->addParserOutputMetadata( $stubPOEmpty );
1381 $this->assertSame( $finalIds, $op->getTemplateIds() );
1389 public function testFileSearchOptions() {
1390 $op = $this->newInstance();
1391 $this->assertSame( [], $op->getFileSearchOptions() );
1394 $stubPOEmpty = $this->createParserOutputStub();
1396 $op->addParserOutputMetadata( $stubPOEmpty );
1397 $this->assertSame( [], $op->getFileSearchOptions() );
1401 'A' => [
'time' =>
null,
'sha1' =>
'' ],
1403 'time' =>
'12211221123321',
1404 'sha1' =>
'bf3ffa7047dc080f5855377a4f83cd18887e3b05',
1408 $stubPO1 = $this->createParserOutputStub(
'getFileSearchOptions', $files1 );
1410 $op->addParserOutput( $stubPO1 );
1411 $this->assertSame( $files1, $op->getFileSearchOptions() );
1415 'C' => [
'time' =>
null,
'sha1' =>
'' ],
1416 'B' => [
'time' =>
null,
'sha1' =>
'' ],
1419 $stubPO2 = $this->createParserOutputStub(
'getFileSearchOptions', $files2 );
1421 $op->addParserOutputMetadata( $stubPO2 );
1422 $this->assertSame( array_merge( $files1, $files2 ), $op->getFileSearchOptions() );
1425 $op->addParserOutput( $stubPOEmpty );
1426 $this->assertSame( array_merge( $files1, $files2 ), $op->getFileSearchOptions() );
1441 public function testAddWikiText( $method,
array $args, $expected ) {
1442 $op = $this->newInstance();
1443 $this->assertSame(
'', $op->getHTML() );
1451 [
'addWikiTextWithTitle',
'addWikiTextTitleTidy',
'addWikiTextTitle' ]
1454 $args[1] = $op->getTitle();
1458 [
'addWikiTextAsInterface',
'addWikiTextAsContent' ]
1461 $args[2] = $op->getTitle();
1464 $op->$method( ...
$args );
1465 $this->assertSame( $expected, $op->getHTML() );
1468 public function provideAddWikiText() {
1472 'Simple wikitext' => [
1474 "<p><b>Bold</b>\n</p>",
1475 ],
'List at start' => [
1477 "<ul><li>List</li></ul>\n",
1478 ],
'List not at start' => [
1479 [
'* Not a list',
false ],
1481 ],
'Non-interface' => [
1482 [
"'''Bold'''",
true,
false ],
1483 "<p><b>Bold</b>\n</p>",
1484 ],
'No section edit links' => [
1486 "<h2><span class=\"mw-headline\" id=\"Title\">Title</span></h2>\n",
1489 'addWikiTextWithTitle' => [
1491 'With title at start' => [
1493 "<ul><li>Some page</li></ul>\n",
1494 ],
'With title at start' => [
1499 'addWikiTextAsInterface' => [
1501 'Simple wikitext' => [
1503 "<p><b>Bold</b>\n</p>",
1504 ],
'Untidy wikitext' => [
1506 "<p><b>Bold\n</b></p>",
1507 ],
'List at start' => [
1509 "<ul><li>List</li></ul>\n",
1510 ],
'List not at start' => [
1511 [
'* Not a list',
false ],
1512 '<p>* Not a list</p>',
1513 ],
'No section edit links' => [
1515 "<h2><span class=\"mw-headline\" id=\"Title\">Title</span></h2>\n",
1516 ],
'With title at start' => [
1518 "<ul><li>Some page</li></ul>\n",
1519 ],
'With title at start' => [
1521 "<p>* Some page</p>",
1522 ],
'Untidy input' => [
1524 "<p><b>Some page\n</b></p>",
1527 'addWikiTextAsContent' => [
1529 'SpecialNewimages' => [
1530 [
"<p lang='en' dir='ltr'>\nMy message" ],
1531 '<p lang="en" dir="ltr">' .
"\nMy message\n</p>"
1532 ],
'List at start' => [
1534 "<ul><li>List</li></ul>\n",
1535 ],
'List not at start' => [
1536 [
'* <b>Not a list',
false ],
1537 '<p>* <b>Not a list</b></p>',
1538 ],
'With title at start' => [
1540 "<ul><li>Some page</li></ul>\n",
1541 ],
'With title at start' => [
1543 "<p>* Some page</p>",
1545 [
"<div class='mw-editintro'>{{PAGENAME}}",
true,
Title::newFromText(
'Talk:Some page' ) ],
1546 '<div class="mw-editintro">' .
"Some page\n</div>"
1549 'wrapWikiTextAsInterface' => [
1551 [
'wrapperClass',
'text' ],
1552 "<div class=\"wrapperClass\"><p>text\n</p></div>"
1553 ],
'Spurious </div>' => [
1554 [
'wrapperClass',
'text</div><div>more' ],
1555 "<div class=\"wrapperClass\"><p>text</p><div>more\n</div></div>"
1556 ],
'Extra newlines would break <p> wrappers' => [
1557 [
'two classes',
"1\n\n2\n\n3" ],
1558 "<div class=\"two classes\"><p>1\n</p><p>2\n</p><p>3\n</p></div>"
1559 ],
'Other unclosed tags' => [
1560 [
'error',
'a<b>c<i>d' ],
1561 "<div class=\"error\"><p>a<b>c<i>d\n</i></b></p></div>"
1567 foreach ( $tests[
'addWikiText']
as $key => $val ) {
1568 $args = [ $val[0][0],
null, $val[0][1] ??
true,
false, $val[0][2] ??
true ];
1569 $tests[
'addWikiTextTitle'][
"$key (addWikiTextTitle)"] =
1570 array_merge( [
$args ], array_slice( $val, 1 ) );
1572 foreach ( $tests[
'addWikiTextWithTitle']
as $key => $val ) {
1573 $args = [ $val[0][0], $val[0][1], $val[0][2] ??
true ];
1574 $tests[
'addWikiTextTitle'][
"$key (addWikiTextTitle)"] =
1575 array_merge( [
$args ], array_slice( $val, 1 ) );
1577 foreach ( $tests[
'addWikiTextAsInterface']
as $key => $val ) {
1578 $args = [ $val[0][0], $val[0][2] ??
null, $val[0][1] ??
true,
true,
true ];
1579 $tests[
'addWikiTextTitle'][
"$key (addWikiTextTitle)"] =
1580 array_merge( [
$args ], array_slice( $val, 1 ) );
1582 foreach ( $tests[
'addWikiTextAsContent']
as $key => $val ) {
1583 $args = [ $val[0][0], $val[0][2] ??
null, $val[0][1] ??
true,
true,
false ];
1584 $tests[
'addWikiTextTitle'][
"$key (addWikiTextTitle)"] =
1585 array_merge( [
$args ], array_slice( $val, 1 ) );
1589 foreach ( $tests[
'addWikiTextAsContent']
as $key => $val ) {
1590 if (
count( $val[0] ) > 2 ) {
1591 $args = [ $val[0][0], $val[0][2], $val[0][1] ??
true ];
1592 $tests[
'addWikiTextTitleTidy'][
"$key (addWikiTextTitleTidy)"] =
1593 array_merge( [
$args ], array_slice( $val, 1 ) );
1595 $args = [ $val[0][0], $val[0][1] ??
true ];
1596 $tests[
'addWikiTextTidy'][
"$key (addWikiTextTidy)"] =
1597 array_merge( [
$args ], array_slice( $val, 1 ) );
1603 foreach ( $tests
as $key => $subarray ) {
1604 foreach ( $subarray
as $subkey => $val ) {
1605 $val = array_merge( [ $key ], $val );
1606 $ret[$subkey] = $val;
1616 public function testAddWikiTextNoTitle() {
1619 $op = $this->newInstance( [],
null,
'notitle' );
1620 $op->addWikiText(
'a' );
1626 public function testAddWikiTextAsInterfaceNoTitle() {
1629 $op = $this->newInstance( [],
null,
'notitle' );
1630 $op->addWikiTextAsInterface(
'a' );
1636 public function testAddWikiTextAsContentNoTitle() {
1639 $op = $this->newInstance( [],
null,
'notitle' );
1640 $op->addWikiTextAsContent(
'a' );
1646 public function testAddWikiMsg() {
1648 $this->assertSame(
'(a)', $msg->rawParams(
'a' )->plain() );
1650 $op = $this->newInstance();
1651 $this->assertSame(
'', $op->getHTML() );
1652 $op->addWikiMsg(
'parentheses',
"<b>a" );
1655 $this->assertSame(
"<p>(<b>a)\n</p>", $op->getHTML() );
1661 public function testWrapWikiMsg() {
1663 $this->assertSame(
'(a)', $msg->rawParams(
'a' )->plain() );
1665 $op = $this->newInstance();
1666 $this->assertSame(
'', $op->getHTML() );
1667 $op->wrapWikiMsg(
'[$1]', [
'parentheses',
"<b>a" ] );
1670 $this->assertSame(
"<p>[(<b>a)]\n</p>", $op->getHTML() );
1677 public function testNoGallery() {
1678 $op = $this->newInstance();
1679 $this->assertFalse( $op->mNoGallery );
1681 $stubPO1 = $this->createParserOutputStub(
'getNoGallery',
true );
1682 $op->addParserOutputMetadata( $stubPO1 );
1683 $this->assertTrue( $op->mNoGallery );
1685 $stubPO2 = $this->createParserOutputStub(
'getNoGallery',
false );
1686 $op->addParserOutput( $stubPO2 );
1687 $this->assertFalse( $op->mNoGallery );
1690 private static $parserOutputHookCalled;
1695 public function testParserOutputHooks() {
1696 $op = $this->newInstance();
1697 $pOut = $this->createParserOutputStub(
'getOutputHooks', [
1698 [
'myhook',
'banana' ],
1699 [
'yourhook',
'kumquat' ],
1700 [
'theirhook',
'hippopotamus' ],
1703 self::$parserOutputHookCalled = [];
1706 'myhook' =>
function ( OutputPage $innerOp,
ParserOutput $innerPOut, $data )
1707 use ( $op, $pOut ) {
1708 $this->assertSame( $op, $innerOp );
1709 $this->assertSame( $pOut, $innerPOut );
1710 $this->assertSame(
'banana', $data );
1711 self::$parserOutputHookCalled[] =
'closure';
1713 'yourhook' => [ $this,
'parserOutputHookCallback' ],
1714 'theirhook' => [ __CLASS__,
'parserOutputHookCallbackStatic' ],
1715 'uncalled' =>
function () {
1716 $this->assertTrue(
false );
1720 $op->addParserOutputMetadata( $pOut );
1722 $this->assertSame( [
'closure',
'callback',
'static' ], self::$parserOutputHookCalled );
1725 public function parserOutputHookCallback(
1728 $this->assertSame(
'kumquat', $data );
1730 self::$parserOutputHookCalled[] =
'callback';
1733 public static function parserOutputHookCallbackStatic(
1737 self::assertSame(
'hippopotamus', $data );
1739 self::$parserOutputHookCalled[] =
'static';
1755 public function testAddParserOutputText() {
1756 $op = $this->newInstance();
1757 $this->assertSame(
'', $op->getHTML() );
1759 $pOut = $this->createParserOutputStub(
'getText',
'<some text>' );
1761 $op->addParserOutputMetadata( $pOut );
1762 $this->assertSame(
'', $op->getHTML() );
1764 $op->addParserOutputText( $pOut );
1765 $this->assertSame(
'<some text>', $op->getHTML() );
1771 public function testAddParserOutput() {
1772 $op = $this->newInstance();
1773 $this->assertSame(
'', $op->getHTML() );
1774 $this->assertFalse( $op->showNewSectionLink() );
1776 $pOut = $this->createParserOutputStub( [
1777 'getText' =>
'<some text>',
1778 'getNewSection' =>
true,
1781 $op->addParserOutput( $pOut );
1782 $this->assertSame(
'<some text>', $op->getHTML() );
1783 $this->assertTrue( $op->showNewSectionLink() );
1789 public function testAddTemplate() {
1791 $template->method(
'getHTML' )->willReturn(
'<abc>&def;' );
1793 $op = $this->newInstance();
1796 $this->assertSame(
'<abc>&def;', $op->getHTML() );
1806 public function testParse(
array $args, $expectedHTML ) {
1807 $op = $this->newInstance();
1808 $this->assertSame( $expectedHTML, $op->parse( ...$args ) );
1815 public function testParseInline(
array $args, $expectedHTML, $expectedHTMLInline =
null ) {
1818 $this->assertTrue(
true );
1821 $op = $this->newInstance();
1822 $this->assertSame( $expectedHTMLInline ?? $expectedHTML, $op->parseInline( ...$args ) );
1825 public function provideParse() {
1827 'List at start of line (content)' => [
1828 [
'* List',
true,
false ],
1829 "<div class=\"mw-parser-output\"><ul><li>List</li></ul>\n</div>",
1830 "<ul><li>List</li></ul>\n",
1832 'List at start of line (interface)' => [
1833 [
'* List',
true,
true ],
1834 "<ul><li>List</li></ul>\n",
1836 'List not at start (content)' => [
1837 [
"* ''Not'' list",
false,
false ],
1838 '<div class="mw-parser-output">* <i>Not</i> list</div>',
1839 '* <i>Not</i> list',
1841 'List not at start (interface)' => [
1842 [
"* ''Not'' list",
false,
true ],
1843 '* <i>Not</i> list',
1845 'Interface message' => [
1846 [
"''Italic''",
true,
true ],
1847 "<p><i>Italic</i>\n</p>",
1850 'formatnum (content)' => [
1851 [
'{{formatnum:123456.789}}',
true,
false ],
1852 "<div class=\"mw-parser-output\"><p>123,456.789\n</p></div>",
1855 'formatnum (interface)' => [
1856 [
'{{formatnum:123456.789}}',
true,
true ],
1857 "<p>123,456.789\n</p>",
1860 'Language (content)' => [
1862 "<div class=\"mw-parser-output\"><p>123.456,789\n</p></div>",
1864 'Language (interface)' => [
1866 "<p>123.456,789\n</p>",
1869 'No section edit links' => [
1871 '<div class="mw-parser-output"><h2><span class="mw-headline" id="Header">' .
1872 "Header</span></h2>\n</div>",
1873 '<h2><span class="mw-headline" id="Header">Header</span></h2>' .
1886 public function testParseAsContent(
1887 array $args, $expectedHTML, $expectedHTMLInline =
null
1889 $op = $this->newInstance();
1890 $this->assertSame( $expectedHTML, $op->parseAsContent( ...$args ) );
1900 public function testParseAsInterface(
1901 array $args, $expectedHTML, $expectedHTMLInline =
null
1903 $op = $this->newInstance();
1904 $this->assertSame( $expectedHTML, $op->parseAsInterface( ...$args ) );
1911 public function testParseInlineAsInterface(
1912 array $args, $expectedHTML, $expectedHTMLInline =
null
1914 $op = $this->newInstance();
1916 $expectedHTMLInline ?? $expectedHTML,
1917 $op->parseInlineAsInterface( ...$args )
1921 public function provideParseAs() {
1923 'List at start of line' => [
1925 "<ul><li>List</li></ul>\n",
1927 'List not at start' => [
1928 [
"* ''Not'' list",
false ],
1929 '<p>* <i>Not</i> list</p>',
1930 '* <i>Not</i> list',
1933 [
"''Italic''",
true ],
1934 "<p><i>Italic</i>\n</p>",
1938 [
'{{formatnum:123456.789}}',
true ],
1939 "<p>123,456.789\n</p>",
1942 'No section edit links' => [
1944 '<h2><span class="mw-headline" id="Header">Header</span></h2>' .
1953 public function testParseNullTitle() {
1954 $this->setExpectedException(
MWException::class,
'Empty $mTitle in OutputPage::parseInternal' );
1955 $op = $this->newInstance( [],
null,
'notitle' );
1962 public function testParseInlineNullTitle() {
1963 $this->setExpectedException(
MWException::class,
'Empty $mTitle in OutputPage::parseInternal' );
1964 $op = $this->newInstance( [],
null,
'notitle' );
1965 $op->parseInline(
'' );
1971 public function testParseAsContentNullTitle() {
1972 $this->setExpectedException(
MWException::class,
'Empty $mTitle in OutputPage::parseInternal' );
1973 $op = $this->newInstance( [],
null,
'notitle' );
1974 $op->parseAsContent(
'' );
1980 public function testParseAsInterfaceNullTitle() {
1981 $this->setExpectedException(
MWException::class,
'Empty $mTitle in OutputPage::parseInternal' );
1982 $op = $this->newInstance( [],
null,
'notitle' );
1983 $op->parseAsInterface(
'' );
1989 public function testParseInlineAsInterfaceNullTitle() {
1990 $this->setExpectedException(
MWException::class,
'Empty $mTitle in OutputPage::parseInternal' );
1991 $op = $this->newInstance( [],
null,
'notitle' );
1992 $op->parseInlineAsInterface(
'' );
1999 public function testCdnMaxage() {
2000 $op = $this->newInstance();
2001 $wrapper = TestingAccessWrapper::newFromObject( $op );
2002 $this->assertSame( 0, $wrapper->mCdnMaxage );
2004 $op->setCdnMaxage( -1 );
2005 $this->assertSame( -1, $wrapper->mCdnMaxage );
2007 $op->setCdnMaxage( 120 );
2008 $this->assertSame( 120, $wrapper->mCdnMaxage );
2010 $op->setCdnMaxage( 60 );
2011 $this->assertSame( 60, $wrapper->mCdnMaxage );
2013 $op->setCdnMaxage( 180 );
2014 $this->assertSame( 180, $wrapper->mCdnMaxage );
2016 $op->lowerCdnMaxage( 240 );
2017 $this->assertSame( 180, $wrapper->mCdnMaxage );
2019 $op->setCdnMaxage( 300 );
2020 $this->assertSame( 240, $wrapper->mCdnMaxage );
2022 $op->lowerCdnMaxage( 120 );
2023 $this->assertSame( 120, $wrapper->mCdnMaxage );
2025 $op->setCdnMaxage( 180 );
2026 $this->assertSame( 120, $wrapper->mCdnMaxage );
2028 $op->setCdnMaxage( 60 );
2029 $this->assertSame( 60, $wrapper->mCdnMaxage );
2031 $op->setCdnMaxage( 240 );
2032 $this->assertSame( 120, $wrapper->mCdnMaxage );
2036 private static $fakeTime;
2048 MWTimestamp::setFakeTime( self::$fakeTime );
2050 $op = $this->newInstance();
2053 $initial =
$options[
'initialMaxage'] ?? 86400;
2054 $op->setCdnMaxage( $initial );
2056 $op->adaptCdnTTL( ...
$args );
2058 MWTimestamp::setFakeTime(
false );
2061 $wrapper = TestingAccessWrapper::newFromObject( $op );
2064 if (
$args[0] ===
null ||
$args[0] ===
false ) {
2065 $this->assertSame( $initial, $wrapper->mCdnMaxage,
'member value' );
2066 $op->setCdnMaxage( $expected + 1 );
2067 $this->assertSame( $expected + 1, $wrapper->mCdnMaxage,
'member value after new set' );
2071 $this->assertSame( $expected, $wrapper->mCdnMaxageLimit,
'limit value' );
2073 if ( $initial >= $expected ) {
2074 $this->assertSame( $expected, $wrapper->mCdnMaxage,
'member value' );
2076 $this->assertSame( $initial, $wrapper->mCdnMaxage,
'member value' );
2079 $op->setCdnMaxage( $expected + 1 );
2080 $this->assertSame( $expected, $wrapper->mCdnMaxage,
'member value after new set' );
2083 public function provideAdaptCdnTTL() {
2086 self::$fakeTime = $now;
2088 'Five minutes ago' => [ [ $now - 300 ], 270 ],
2091 'Five minutes ago, initial maxage four minutes' =>
2092 [ [ $now - 300 ], 270, [
'initialMaxage' => 240 ] ],
2093 'A very long time ago' => [ [ $now - 1000000000 ],
$wgSquidMaxage ],
2094 'Initial maxage zero' => [ [ $now - 300 ], 270, [
'initialMaxage' => 0 ] ],
2105 'Now, minTTL 0.000001' => [ [ $now, 0.000001 ], 0 ],
2106 'A very long time ago, maxTTL even longer' =>
2107 [ [ $now - 1000000000, 0, 1000000001 ], 900000000 ],
2116 public function testClientCache() {
2117 $op = $this->newInstance();
2120 $this->assertSame(
true, $op->enableClientCache(
null ) );
2122 $this->assertSame(
true, $op->enableClientCache(
null ) );
2125 $this->assertSame(
true, $op->enableClientCache(
false ) );
2126 $this->assertSame(
false, $op->enableClientCache(
null ) );
2128 $this->assertSame(
false, $op->enableClientCache(
null ) );
2131 $pOutCacheable = $this->createParserOutputStub(
'isCacheable',
true );
2132 $op->addParserOutputMetadata( $pOutCacheable );
2133 $this->assertSame(
false, $op->enableClientCache(
null ) );
2136 $this->assertSame(
false, $op->enableClientCache(
true ) );
2137 $this->assertSame(
true, $op->enableClientCache(
null ) );
2140 $pOutUncacheable = $this->createParserOutputStub(
'isCacheable',
false );
2141 $op->addParserOutput( $pOutUncacheable );
2142 $this->assertSame(
false, $op->enableClientCache(
null ) );
2148 public function testGetCacheVaryCookies() {
2150 $op = $this->newInstance();
2152 $expectedCookies = [
2154 "{$prefix}LoggedOut",
2155 "{$prefix}_session",
2162 TestingAccessWrapper::newFromClass(
OutputPage::class )->cacheVaryCookies =
null;
2164 $this->
setMwGlobals(
'wgCacheVaryCookies', [
'cookie1' ] );
2166 function ( $innerOP, &$cookies )
use ( $op, $expectedCookies ) {
2167 $this->assertSame( $op, $innerOP );
2168 $cookies[] =
'cookie2';
2169 $this->assertSame( $expectedCookies, $cookies );
2173 $this->assertSame( $expectedCookies, $op->getCacheVaryCookies() );
2179 public function testHaveCacheVaryCookies() {
2181 $op = $this->newInstance( [],
$request );
2184 $this->assertFalse( $op->haveCacheVaryCookies() );
2187 $request->setCookie(
'Token',
'' );
2188 $this->assertFalse( $op->haveCacheVaryCookies() );
2191 $request->setCookie(
'Token',
'123' );
2192 $this->assertTrue( $op->haveCacheVaryCookies() );
2207 public function testVaryHeaders(
array $calls,
array $cookies, $vary, $key ) {
2211 ->setMethods( [
'getCacheVaryCookies' ] )
2213 $op->expects( $this->
any() )
2214 ->method(
'getCacheVaryCookies' )
2215 ->will( $this->returnValue( $cookies ) );
2216 TestingAccessWrapper::newFromObject( $op )->mVaryHeader = [];
2219 foreach ( $calls
as $call ) {
2220 $op->addVaryHeader( ...$call );
2222 $this->assertEquals( $vary, $op->getVaryHeader(),
'Vary:' );
2223 $this->assertEquals( $key, $op->getKeyHeader(),
'Key:' );
2226 public function provideVaryHeaders() {
2235 'Single header' => [
2243 'Non-unique headers' => [
2246 [
'Accept-Language' ],
2250 'Vary: Cookie, Accept-Language',
2251 'Key: Cookie,Accept-Language',
2253 'Two headers with single options' => [
2255 [
'Cookie', [
'param=phpsessid' ] ],
2256 [
'Accept-Language', [
'substr=en' ] ],
2259 'Vary: Cookie, Accept-Language',
2260 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
2262 'One header with multiple options' => [
2264 [
'Cookie', [
'param=phpsessid',
'param=userId' ] ],
2268 'Key: Cookie;param=phpsessid;param=userId',
2270 'Duplicate option' => [
2272 [
'Cookie', [
'param=phpsessid' ] ],
2273 [
'Cookie', [
'param=phpsessid' ] ],
2274 [
'Accept-Language', [
'substr=en',
'substr=en' ] ],
2277 'Vary: Cookie, Accept-Language',
2278 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
2280 'Same header, different options' => [
2282 [
'Cookie', [
'param=phpsessid' ] ],
2283 [
'Cookie', [
'param=userId' ] ],
2287 'Key: Cookie;param=phpsessid;param=userId',
2289 'No header, vary cookies' => [
2291 [
'cookie1',
'cookie2' ],
2293 'Key: Cookie;param=cookie1;param=cookie2',
2295 'Cookie header with option plus vary cookies' => [
2297 [
'Cookie', [
'param=cookie1' ] ],
2299 [
'cookie2',
'cookie3' ],
2301 'Key: Cookie;param=cookie1;param=cookie2;param=cookie3',
2303 'Non-cookie header plus vary cookies' => [
2305 [
'Accept-Language' ],
2308 'Vary: Accept-Language, Cookie',
2309 'Key: Accept-Language,Cookie;param=cookie',
2311 'Cookie and non-cookie headers plus vary cookies' => [
2313 [
'Cookie', [
'param=cookie1' ] ],
2314 [
'Accept-Language' ],
2317 'Vary: Cookie, Accept-Language',
2318 'Key: Cookie;param=cookie1;param=cookie2,Accept-Language',
2326 public function testVaryHeaderDefault() {
2327 $op = $this->newInstance();
2328 $this->assertSame(
'Vary: Accept-Encoding, Cookie', $op->getVaryHeader() );
2337 public function testLinkHeaders(
array $headers,
$result ) {
2338 $op = $this->newInstance();
2341 $op->addLinkHeader(
$header );
2344 $this->assertEquals(
$result, $op->getLinkHeader() );
2347 public function provideLinkHeaders() {
2354 [
'<https://foo/bar.jpg>;rel=preload;as=image' ],
2355 'Link: <https://foo/bar.jpg>;rel=preload;as=image',
2359 '<https://foo/bar.jpg>;rel=preload;as=image',
2360 '<https://foo/baz.jpg>;rel=preload;as=image'
2362 'Link: <https://foo/bar.jpg>;rel=preload;as=image,<https://foo/baz.jpg>;' .
2363 'rel=preload;as=image',
2373 public function testAddAcceptLanguage(
2377 $op = $this->newInstance( [],
$req, in_array(
'notitle',
$options ) ?
'notitle' :
null );
2379 if ( !in_array(
'notitle',
$options ) ) {
2382 if ( in_array(
'varianturl',
$options ) ) {
2383 $mockLang->expects( $this->never() )->method( $this->
anything() );
2385 $mockLang->method(
'hasVariants' )->willReturn(
count( $variants ) > 1 );
2386 $mockLang->method(
'getVariants' )->willReturn( $variants );
2387 $mockLang->method(
'getCode' )->willReturn(
$code );
2391 $mockTitle->method(
'getPageLanguage' )->willReturn( $mockLang );
2393 $op->setTitle( $mockTitle );
2397 $op->sendCacheControl();
2400 $keyHeader = $op->getKeyHeader();
2403 $this->assertFalse( strpos(
'Accept-Language', $keyHeader ) );
2407 $keyHeader = explode(
' ', $keyHeader, 2 )[1];
2408 $keyHeader = explode(
',', $keyHeader );
2410 $acceptLanguage =
null;
2411 foreach ( $keyHeader
as $item ) {
2412 if ( strpos( $item,
'Accept-Language;' ) === 0 ) {
2413 $acceptLanguage = $item;
2418 $expectedString =
'Accept-Language;substr=' . implode(
';substr=', $expected );
2419 $this->assertSame( $expectedString, $acceptLanguage );
2422 public function provideAddAcceptLanguage() {
2424 'No variants' => [
'en', [
'en' ], [] ],
2425 'One simple variant' => [
'en', [
'en',
'en-x-piglatin' ], [
'en-x-piglatin' ] ],
2426 'Multiple variants with BCP47 alternatives' => [
2428 [
'zh',
'zh-hans',
'zh-cn',
'zh-tw' ],
2429 [
'zh-hans',
'zh-Hans',
'zh-cn',
'zh-Hans-CN',
'zh-tw',
'zh-Hant-TW' ],
2431 'No title' => [
'en', [
'en',
'en-x-piglatin' ], [], [
'notitle' ] ],
2432 'Variant in URL' => [
'en', [
'en',
'en-x-piglatin' ], [], [
'varianturl' ] ],
2443 public function testClickjacking() {
2444 $op = $this->newInstance();
2445 $this->assertTrue( $op->getPreventClickjacking() );
2447 $op->allowClickjacking();
2448 $this->assertFalse( $op->getPreventClickjacking() );
2450 $op->preventClickjacking();
2451 $this->assertTrue( $op->getPreventClickjacking() );
2453 $op->preventClickjacking(
false );
2454 $this->assertFalse( $op->getPreventClickjacking() );
2456 $pOut1 = $this->createParserOutputStub(
'preventClickjacking',
true );
2457 $op->addParserOutputMetadata( $pOut1 );
2458 $this->assertTrue( $op->getPreventClickjacking() );
2461 $pOut2 = $this->createParserOutputStub(
'preventClickjacking',
false );
2462 $op->addParserOutputMetadata( $pOut2 );
2463 $this->assertTrue( $op->getPreventClickjacking() );
2466 $op->allowClickjacking();
2467 $this->assertFalse( $op->getPreventClickjacking() );
2469 $op->addParserOutput( $pOut1 );
2470 $this->assertTrue( $op->getPreventClickjacking() );
2472 $op->addParserOutput( $pOut2 );
2473 $this->assertTrue( $op->getPreventClickjacking() );
2481 public function testGetFrameOptions(
2482 $breakFrames, $preventClickjacking, $editPageFrameOptions, $expected
2484 $op = $this->newInstance( [
2485 'BreakFrames' => $breakFrames,
2486 'EditPageFrameOptions' => $editPageFrameOptions,
2488 $op->preventClickjacking( $preventClickjacking );
2490 $this->assertSame( $expected, $op->getFrameOptions() );
2493 public function provideGetFrameOptions() {
2495 'BreakFrames true' => [
true,
false,
false,
'DENY' ],
2496 'Allow clickjacking locally' => [
false,
false,
'DENY',
false ],
2497 'Allow clickjacking globally' => [
false,
true,
false,
false ],
2498 'DENY globally' => [
false,
true,
'DENY',
'DENY' ],
2499 'SAMEORIGIN' => [
false,
true,
'SAMEORIGIN',
'SAMEORIGIN' ],
2500 'BreakFrames with SAMEORIGIN' => [
true,
true,
'SAMEORIGIN',
'DENY' ],
2511 public function testMakeResourceLoaderLink(
$args, $expectedHtml ) {
2513 'wgResourceLoaderDebug' =>
false,
2514 'wgLoadScript' =>
'http://127.0.0.1:8080/w/load.php',
2515 'wgCSPReportOnlyHeader' =>
true,
2518 $method = $class->getMethod(
'makeResourceLoaderLink' );
2519 $method->setAccessible(
true );
2522 $ctx->setLanguage(
'en' );
2523 $out =
new OutputPage( $ctx );
2524 $nonce = $class->getProperty(
'CSPNonce' );
2525 $nonce->setAccessible(
true );
2526 $nonce->setValue(
$out,
'secret' );
2527 $rl =
$out->getResourceLoader();
2528 $rl->setMessageBlobStore(
new NullMessageBlobStore() );
2531 'script' =>
'mw.test.foo( { a: true } );',
2532 'styles' =>
'.mw-test-foo { content: "style"; }',
2535 'script' =>
'mw.test.bar( { a: true } );',
2536 'styles' =>
'.mw-test-bar { content: "style"; }',
2539 'script' =>
'mw.test.baz( { a: true } );',
2540 'styles' =>
'.mw-test-baz { content: "style"; }',
2543 'script' =>
'mw.test.baz( { token: 123 } );',
2544 'styles' =>
'/* pref-animate=off */ .mw-icon { transition: none; }',
2545 'group' =>
'private',
2548 'styles' =>
'.stuff { color: red; }',
2549 'group' =>
'noscript',
2552 'script' =>
'mw.doStuff( "foo" );',
2556 'script' =>
'mw.doStuff( "bar" );',
2560 $links = $method->invokeArgs(
$out,
$args );
2561 $actualHtml = strval( $links );
2562 $this->assertEquals( $expectedHtml, $actualHtml );
2565 public static function provideMakeResourceLoaderLink() {
2571 "<script nonce=\"secret\">(window.RLQ=window.RLQ||[]).push(function(){"
2572 .
'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback");'
2579 '<link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.bar%2Cbaz%2Cfoo&only=styles&skin=fallback"/>'
2584 "<script nonce=\"secret\">(window.RLQ=window.RLQ||[]).push(function(){"
2585 .
"mw.test.baz({token:123});\nmw.loader.state({\"test.quux\":\"ready\"});"
2591 "<script nonce=\"secret\">(window.RLQ=window.RLQ||[]).push(function(){"
2592 .
"mw.loader.implement(\"test.quux@1ev0ijv\",function($,jQuery,require,module){"
2593 .
"mw.test.baz({token:123});},{\"css\":[\".mw-icon{transition:none}"
2594 .
"\"]});});</script>"
2604 '<noscript><link rel="stylesheet" href="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.noscript&only=styles&skin=fallback"/></noscript>'
2609 "<script nonce=\"secret\">(window.RLQ=window.RLQ||[]).push(function(){"
2610 .
'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.bar\u0026skin=fallback");'
2611 .
'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.foo\u0026skin=fallback");'
2623 public function testBuildExemptModules(
array $exemptStyleModules, $expect ) {
2625 'wgResourceLoaderDebug' =>
false,
2626 'wgLoadScript' =>
'/w/load.php',
2629 'wgCacheEpoch' =>
'20140101000000',
2635 $ctx->setLanguage(
'en' );
2637 ->setConstructorArgs( [ $ctx ] )
2638 ->setMethods( [
'buildCssLinksArray' ] )
2640 $op->expects( $this->
any() )
2641 ->method(
'buildCssLinksArray' )
2643 $rl = $op->getResourceLoader();
2644 $rl->setMessageBlobStore(
new NullMessageBlobStore() );
2653 $op = TestingAccessWrapper::newFromObject( $op );
2654 $op->rlExemptStyleModules = $exemptStyleModules;
2655 $this->assertEquals(
2657 strval( $op->buildExemptModules() )
2661 public static function provideBuildExemptModules() {
2665 'exemptStyleModules' => [],
2666 '<meta name="ResourceLoaderDynamicStyles" content=""/>',
2669 'exemptStyleModules' => [
'site' => [],
'noscript' => [],
'private' => [],
'user' => [] ],
2670 '<meta name="ResourceLoaderDynamicStyles" content=""/>',
2672 'default logged-out' => [
2673 'exemptStyleModules' => [
'site' => [
'site.styles' ] ],
2674 '<meta name="ResourceLoaderDynamicStyles" content=""/>' .
"\n" .
2675 '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=site.styles&only=styles&skin=fallback"/>',
2677 'default logged-in' => [
2678 'exemptStyleModules' => [
'site' => [
'site.styles' ],
'user' => [
'user.styles' ] ],
2679 '<meta name="ResourceLoaderDynamicStyles" content=""/>' .
"\n" .
2680 '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=site.styles&only=styles&skin=fallback"/>' .
"\n" .
2681 '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=user.styles&only=styles&skin=fallback&version=1ai9g6t"/>',
2683 'custom modules' => [
2684 'exemptStyleModules' => [
2685 'site' => [
'site.styles',
'example.site.a',
'example.site.b' ],
2686 'user' => [
'user.styles',
'example.user' ],
2688 '<meta name="ResourceLoaderDynamicStyles" content=""/>' .
"\n" .
2689 '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=example.site.a%2Cb&only=styles&skin=fallback"/>' .
"\n" .
2690 '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=site.styles&only=styles&skin=fallback"/>' .
"\n" .
2691 '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=example.user&only=styles&skin=fallback&version=0a56zyi"/>' .
"\n" .
2692 '<link rel="stylesheet" href="/w/load.php?debug=false&lang=en&modules=user.styles&only=styles&skin=fallback&version=1ai9g6t"/>',
2703 public function testTransformResourcePath( $baseDir,
$basePath, $uploadDir =
null,
2704 $uploadPath =
null,
$path =
null, $expected =
null
2706 if (
$path ===
null ) {
2709 $expected = $uploadPath;
2710 $uploadDir =
"$baseDir/images";
2711 $uploadPath =
"$basePath/images";
2716 'UploadDirectory' => $uploadDir,
2717 'UploadPath' => $uploadPath,
2721 Wikimedia\suppressWarnings();
2722 $actual = OutputPage::transformResourcePath( $conf,
$path );
2723 Wikimedia\restoreWarnings();
2725 $this->assertEquals( $expected ?:
$path, $actual );
2728 public static function provideTransformFilePath() {
2729 $baseDir = dirname( __DIR__ ) .
'/data/media';
2733 'baseDir' => $baseDir,
'basePath' =>
'/w',
2739 'baseDir' => $baseDir,
'basePath' =>
'/w',
2745 'baseDir' => $baseDir,
'basePath' =>
'/w',
2750 'baseDir' => $baseDir,
'basePath' =>
'/w',
2756 'baseDir' => $baseDir,
'basePath' =>
'/w',
2757 '//example.org/w/test.jpg'
2760 'baseDir' => $baseDir,
'basePath' =>
'/w',
2761 'https://example.org/w/test.jpg'
2765 'baseDir' => $baseDir,
'basePath' =>
'/w',
2766 'https://example.org/files/test.jpg'
2769 'baseDir' => $baseDir,
'basePath' =>
'/w',
2770 '//example.org/files/test.jpg'
2774 'baseDir' => $baseDir,
'basePath' =>
'',
2775 'https://example.org/files/test.jpg'
2778 'baseDir' => $baseDir,
'basePath' =>
'',
2780 '//example.org/files/test.jpg'
2784 'baseDir' => dirname( $baseDir ),
'basePath' =>
'',
2785 'uploadDir' => $baseDir,
'uploadPath' =>
'/images',
2787 '/images/test.jpg?edcf2'
2806 protected function assertTransformCssMediaCase(
$args ) {
2808 if ( isset(
$args[
'printableQuery'] ) ) {
2809 $queryData[
'printable'] =
$args[
'printableQuery'];
2812 if ( isset(
$args[
'handheldQuery'] ) ) {
2813 $queryData[
'handheld'] =
$args[
'handheldQuery'];
2816 $fauxRequest =
new FauxRequest( $queryData,
false );
2818 'wgRequest' => $fauxRequest,
2821 $actualReturn = OutputPage::transformCssMedia(
$args[
'media'] );
2822 $this->assertSame(
$args[
'expectedReturn'], $actualReturn,
$args[
'message'] );
2830 public function testPrintRequests() {
2831 $this->assertTransformCssMediaCase( [
2832 'printableQuery' =>
'1',
2833 'media' =>
'screen',
2834 'expectedReturn' =>
null,
2835 'message' =>
'On printable request, screen returns null'
2838 $this->assertTransformCssMediaCase( [
2839 'printableQuery' =>
'1',
2840 'media' => self::SCREEN_MEDIA_QUERY,
2841 'expectedReturn' =>
null,
2842 'message' =>
'On printable request, screen media query returns null'
2845 $this->assertTransformCssMediaCase( [
2846 'printableQuery' =>
'1',
2847 'media' => self::SCREEN_ONLY_MEDIA_QUERY,
2848 'expectedReturn' =>
null,
2849 'message' =>
'On printable request, screen media query with only returns null'
2852 $this->assertTransformCssMediaCase( [
2853 'printableQuery' =>
'1',
2855 'expectedReturn' =>
'',
2856 'message' =>
'On printable request, media print returns empty string'
2865 public function testScreenRequests() {
2866 $this->assertTransformCssMediaCase( [
2867 'media' =>
'screen',
2868 'expectedReturn' =>
'screen',
2869 'message' =>
'On screen request, screen media type is preserved'
2872 $this->assertTransformCssMediaCase( [
2873 'media' =>
'handheld',
2874 'expectedReturn' =>
'handheld',
2875 'message' =>
'On screen request, handheld media type is preserved'
2878 $this->assertTransformCssMediaCase( [
2879 'media' => self::SCREEN_MEDIA_QUERY,
2880 'expectedReturn' => self::SCREEN_MEDIA_QUERY,
2881 'message' =>
'On screen request, screen media query is preserved.'
2884 $this->assertTransformCssMediaCase( [
2885 'media' => self::SCREEN_ONLY_MEDIA_QUERY,
2886 'expectedReturn' => self::SCREEN_ONLY_MEDIA_QUERY,
2887 'message' =>
'On screen request, screen media query with only is preserved.'
2890 $this->assertTransformCssMediaCase( [
2892 'expectedReturn' =>
'print',
2893 'message' =>
'On screen request, print media type is preserved'
2902 public function testHandheld() {
2903 $this->assertTransformCssMediaCase( [
2904 'handheldQuery' =>
'1',
2905 'media' =>
'handheld',
2906 'expectedReturn' =>
'',
2907 'message' =>
'On request with handheld querystring and media is handheld, returns empty string'
2910 $this->assertTransformCssMediaCase( [
2911 'handheldQuery' =>
'1',
2912 'media' =>
'screen',
2913 'expectedReturn' =>
null,
2914 'message' =>
'On request with handheld querystring and media is screen, returns null'
2923 public function testIsTOCEnabled() {
2924 $op = $this->newInstance();
2925 $this->assertFalse( $op->isTOCEnabled() );
2927 $pOut1 = $this->createParserOutputStub(
'getTOCHTML',
false );
2928 $op->addParserOutputMetadata( $pOut1 );
2929 $this->assertFalse( $op->isTOCEnabled() );
2931 $pOut2 = $this->createParserOutputStub(
'getTOCHTML',
true );
2932 $op->addParserOutput( $pOut2 );
2933 $this->assertTrue( $op->isTOCEnabled() );
2936 $op->addParserOutputMetadata( $pOut1 );
2937 $this->assertTrue( $op->isTOCEnabled() );
2945 public function testPreloadLinkHeaders( $config,
$result ) {
2948 ->disableOriginalConstructor()->getMock();
2951 $this->assertEquals( [
$result ], $module->getHeaders( $ctx ) );
2954 public function providePreloadLinkHeaders() {
2958 'wgResourceBasePath' =>
'/w',
2959 'wgLogo' =>
'/img/default.png',
2961 '1.5x' =>
'/img/one-point-five.png',
2962 '2x' =>
'/img/two-x.png',
2965 'Link: </img/default.png>;rel=preload;as=image;media=' .
2966 'not all and (min-resolution: 1.5dppx),' .
2967 '</img/one-point-five.png>;rel=preload;as=image;media=' .
2968 '(min-resolution: 1.5dppx) and (max-resolution: 1.999999dppx),' .
2969 '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
2973 'wgResourceBasePath' =>
'/w',
2974 'wgLogo' =>
'/img/default.png',
2975 'wgLogoHD' =>
false,
2977 'Link: </img/default.png>;rel=preload;as=image'
2981 'wgResourceBasePath' =>
'/w',
2982 'wgLogo' =>
'/img/default.png',
2984 '2x' =>
'/img/two-x.png',
2987 'Link: </img/default.png>;rel=preload;as=image;media=' .
2988 'not all and (min-resolution: 2dppx),' .
2989 '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
2993 'wgResourceBasePath' =>
'/w',
2994 'wgLogo' =>
'/img/default.png',
2996 'svg' =>
'/img/vector.svg',
2999 'Link: </img/vector.svg>;rel=preload;as=image'
3004 'wgResourceBasePath' =>
'/w',
3005 'wgLogo' =>
'/w/test.jpg',
3006 'wgLogoHD' =>
false,
3007 'wgUploadPath' =>
'/w/images',
3008 'IP' => dirname( __DIR__ ) .
'/data/media',
3010 'Link: </w/test.jpg?edcf2>;rel=preload;as=image',
3023 'AppleTouchIcon' =>
false,
3024 'DisableLangConversion' =>
true,
3025 'EnableCanonicalServerLink' =>
false,
3028 'LanguageCode' =>
false,
3029 'ReferrerPolicy' =>
false,
3030 'RightsPage' =>
false,
3031 'RightsUrl' =>
false,
3032 'UniversalEditButton' =>
false,
3063 public function clear() {