12 protected static $pageId;
13 protected static $revIds = [];
19 self::$pageId =
$status->value[
'revision']->getPage();
20 self::$revIds[
'revdel'] =
$status->value[
'revision']->getId();
23 self::$revIds[
'suppressed'] =
$status->value[
'revision']->getId();
26 self::$revIds[
'oldid'] =
$status->value[
'revision']->getId();
29 self::$revIds[
'latest'] =
$status->value[
'revision']->getId();
33 self::$revIds[
'suppressed'],
51 protected function assertParsedTo( $expected,
array $res, $warnings =
null ) {
52 $this->doAssertParsedTo( $expected,
$res, $warnings, [ $this,
'assertSame' ] );
64 protected function assertParsedToRegExp( $expected,
array $res, $warnings =
null ) {
65 $this->doAssertParsedTo( $expected,
$res, $warnings, [ $this,
'assertRegExp' ] );
68 private function doAssertParsedTo( $expected,
array $res, $warnings, callable $callback ) {
71 $expectedStart =
'<div class="mw-parser-output">';
72 $this->assertSame( $expectedStart, substr(
$html, 0, strlen( $expectedStart ) ) );
74 $html = substr(
$html, strlen( $expectedStart ) );
76 if (
$res[1]->getBool(
'disablelimitreport' ) ) {
77 $expectedEnd =
"</div>";
78 $this->assertSame( $expectedEnd, substr(
$html, -strlen( $expectedEnd ) ) );
80 $unexpectedEnd =
'#<!-- \nNewPP limit report|' .
81 '<!--\nTransclusion expansion time report#s';
82 $this->assertNotRegExp( $unexpectedEnd,
$html );
86 $expectedEnd =
'#\n<!-- \nNewPP limit report\n(?>.+?\n-->)\n' .
87 '<!--\nTransclusion expansion time report \(%,ms,calls,template\)\n(?>.*?\n-->)\n' .
88 '(\n<!-- Saved in parser cache (?>.*?\n -->)\n)?</div>$#s';
89 $this->assertRegExp( $expectedEnd,
$html );
91 $html = preg_replace( $expectedEnd,
'',
$html );
94 call_user_func( $callback, $expected,
$html );
96 if ( $warnings ===
null ) {
97 $this->assertCount( 1,
$res[0] );
99 $this->assertCount( 2,
$res[0] );
100 $this->assertSame( [
'warnings' => $warnings ],
$res[0][
'warnings'][
'parse'] );
107 protected function setupInterwiki() {
112 'iw_prefix' =>
'madeuplanguage',
113 'iw_url' =>
"https://example.com/wiki/$1",
122 $this->
setMwGlobals(
'wgExtraInterlanguageLinkPrefixes', [
'madeuplanguage' ] );
123 $this->tablesUsed[] =
'interwiki';
131 protected function setupSkin() {
133 $factory->register(
'testing',
'Testing',
function () {
135 ->setMethods( [
'getDefaultModules',
'setupSkinUserCss' ] )
137 $skin->expects( $this->once() )->method(
'getDefaultModules' )
139 'styles' => [
'core' => [
'quux.styles' ] ],
140 'core' => [
'foo',
'bar' ],
141 'content' => [
'baz' ]
143 $skin->expects( $this->once() )->method(
'setupSkinUserCss' )
144 ->will( $this->returnCallback(
function ( OutputPage
$out ) {
145 $out->addModuleStyles(
'foo.styles' );
152 public function testParseByName() {
157 $this->assertParsedTo(
"<p>Test for latest\n</p>",
$res );
162 'disablelimitreport' => 1,
164 $this->assertParsedTo(
"<p>Test for latest\n</p>",
$res );
167 public function testParseById() {
170 'pageid' => self::$pageId,
172 $this->assertParsedTo(
"<p>Test for latest\n</p>",
$res );
175 public function testParseByOldId() {
178 'oldid' => self::$revIds[
'oldid'],
180 $this->assertParsedTo(
"<p>Test for oldid\n</p>",
$res );
181 $this->assertArrayNotHasKey(
'textdeleted',
$res[0][
'parse'] );
182 $this->assertArrayNotHasKey(
'textsuppressed',
$res[0][
'parse'] );
185 public function testRevDel() {
188 'oldid' => self::$revIds[
'revdel'],
191 $this->assertParsedTo(
"<p>Test for revdel\n</p>",
$res );
192 $this->assertArrayHasKey(
'textdeleted',
$res[0][
'parse'] );
193 $this->assertArrayNotHasKey(
'textsuppressed',
$res[0][
'parse'] );
196 public function testRevDelNoPermission() {
198 "You don't have permission to view deleted revision text." );
202 'oldid' => self::$revIds[
'revdel'],
203 ],
null,
null, static::getTestUser()->getUser() );
206 public function testSuppressed() {
211 'oldid' => self::$revIds[
'suppressed']
214 $this->assertParsedTo(
"<p>Test for suppressed\n</p>",
$res );
215 $this->assertArrayHasKey(
'textsuppressed',
$res[0][
'parse'] );
216 $this->assertArrayHasKey(
'textdeleted',
$res[0][
'parse'] );
219 public function testNonexistentPage() {
223 'page' =>
'DoesNotExist',
226 $this->fail(
"API did not return an error when parsing a nonexistent page" );
229 "Parse request for nonexistent page must give 'missingtitle' error: "
230 . var_export( self::getErrorFormatter()->arrayFromStatus( $ex->
getStatusValue() ),
true )
235 public function testTitleProvided() {
238 'title' =>
'Some interesting page',
239 'text' =>
'{{PAGENAME}} has attracted my attention',
242 $this->assertParsedTo(
"<p>Some interesting page has attracted my attention\n</p>",
$res );
245 public function testSection() {
246 $name = ucfirst( __FUNCTION__ );
249 "Intro\n\n== Section 1 ==\n\nContent 1\n\n== Section 2 ==\n\nContent 2" );
257 $this->assertParsedToRegExp(
'!<h2>.*Section 1.*</h2>\n<p>Content 1\n</p>!',
$res );
260 public function testInvalidSection() {
262 'The "section" parameter must be a valid section ID or "new".' );
266 'section' =>
'T-new',
270 public function testSectionNoContent() {
271 $name = ucfirst( __FUNCTION__ );
274 "Intro\n\n== Section 1 ==\n\nContent 1\n\n== Section 2 ==\n\nContent 2" );
277 "Missing content for page ID {$status->value['revision']->getPage()}." );
279 $this->db->delete(
'revision', [
'rev_id' =>
$status->value[
'revision']->getId() ] );
282 Wikimedia\suppressWarnings();
290 Wikimedia\restoreWarnings();
294 public function testNewSectionWithPage() {
296 '"section=new" cannot be combined with the "oldid", "pageid" or "page" ' .
297 'parameters. Please use "title" and "text".' );
306 public function testNonexistentOldId() {
308 'There is no revision with ID 2147483647.' );
312 'oldid' => pow( 2, 31 ) - 1,
316 public function testUnfollowedRedirect() {
317 $name = ucfirst( __FUNCTION__ );
320 $this->
editPage(
"$name 2",
"Some ''text''" );
329 $this->assertRegExp(
"/Redirect to:.*$name 2/",
$res[0][
'parse'][
'text'] );
330 $this->assertArrayNotHasKey(
'warnings',
$res[0] );
333 public function testFollowedRedirect() {
334 $name = ucfirst( __FUNCTION__ );
337 $this->
editPage(
"$name 2",
"Some ''text''" );
345 $this->assertParsedTo(
"<p>Some <i>text</i>\n</p>",
$res );
348 public function testFollowedRedirectById() {
349 $name = ucfirst( __FUNCTION__ );
351 $id = $this->
editPage(
$name,
"#REDIRECT [[$name 2]]" )->value[
'revision']->getPage();
352 $this->
editPage(
"$name 2",
"Some ''text''" );
360 $this->assertParsedTo(
"<p>Some <i>text</i>\n</p>",
$res );
363 public function testInvalidTitle() {
372 public function testTitleWithNonexistentRevId() {
374 'There is no revision with ID 2147483647.' );
378 'title' => __CLASS__,
379 'revid' => pow( 2, 31 ) - 1,
383 public function testTitleWithNonMatchingRevId() {
384 $name = ucfirst( __FUNCTION__ );
389 'revid' => self::$revIds[
'latest'],
390 'text' =>
'Some text',
393 $this->assertParsedTo(
"<p>Some text\n</p>",
$res,
394 'r' . self::$revIds[
'latest'] .
" is not a revision of $name." );
397 public function testRevId() {
400 'revid' => self::$revIds[
'latest'],
401 'text' =>
'My revid is {{REVISIONID}}!',
404 $this->assertParsedTo(
"<p>My revid is " . self::$revIds[
'latest'] .
"!\n</p>",
$res );
407 public function testTitleNoText() {
410 'title' =>
'Special:AllPages',
413 $this->assertParsedTo(
'',
$res,
414 '"title" used without "text", and parsed page properties were requested. ' .
415 'Did you mean to use "page" instead of "title"?' );
418 public function testRevidNoText() {
421 'revid' => self::$revIds[
'latest'],
424 $this->assertParsedTo(
'',
$res,
425 '"revid" used without "text", and parsed page properties were requested. ' .
426 'Did you mean to use "oldid" instead of "revid"?' );
429 public function testTextNoContentModel() {
432 'text' =>
"Some ''text''",
435 $this->assertParsedTo(
"<p>Some <i>text</i>\n</p>",
$res,
436 'No "title" or "contentmodel" was given, assuming wikitext.' );
439 public function testSerializationError() {
441 'Content serialization failed: Could not unserialize content' );
444 [
'testing-serialize-error' =>
'DummySerializeErrorContentHandler' ] );
448 'text' =>
"Some ''text''",
449 'contentmodel' =>
'testing-serialize-error',
453 public function testNewSection() {
456 'title' => __CLASS__,
458 'sectiontitle' =>
'Title',
462 $this->assertParsedToRegExp(
'!<h2>.*Title.*</h2>\n<p>Content\n</p>!',
$res );
465 public function testExistingSection() {
468 'title' => __CLASS__,
470 'text' =>
"Intro\n\n== Section 1 ==\n\nContent\n\n== Section 2 ==\n\nMore content",
473 $this->assertParsedToRegExp(
'!<h2>.*Section 1.*</h2>\n<p>Content\n</p>!',
$res );
476 public function testNoPst() {
477 $name = ucfirst( __FUNCTION__ );
479 $this->
editPage(
"Template:$name",
"Template ''text''" );
483 'text' =>
"{{subst:$name}}",
484 'contentmodel' =>
'wikitext',
487 $this->assertParsedTo(
"<p>{{subst:$name}}\n</p>",
$res );
490 public function testPst() {
491 $name = ucfirst( __FUNCTION__ );
493 $this->
editPage(
"Template:$name",
"Template ''text''" );
498 'text' =>
"{{subst:$name}}",
499 'contentmodel' =>
'wikitext',
500 'prop' =>
'text|wikitext',
503 $this->assertParsedTo(
"<p>Template <i>text</i>\n</p>",
$res );
504 $this->assertSame(
"{{subst:$name}}",
$res[0][
'parse'][
'wikitext'] );
507 public function testOnlyPst() {
508 $name = ucfirst( __FUNCTION__ );
510 $this->
editPage(
"Template:$name",
"Template ''text''" );
515 'text' =>
"{{subst:$name}}",
516 'contentmodel' =>
'wikitext',
517 'prop' =>
'text|wikitext',
518 'summary' =>
'Summary',
523 'text' =>
"Template ''text''",
524 'wikitext' =>
"{{subst:$name}}",
525 'parsedsummary' =>
'Summary',
531 public function testHeadHtml() {
535 'prop' =>
'headhtml',
539 $this->assertRegExp(
'#<!DOCTYPE.*<html.*<head.*</head>.*<body#s',
540 $res[0][
'parse'][
'headhtml'] );
541 $this->assertArrayNotHasKey(
'warnings',
$res[0] );
544 public function testCategoriesHtml() {
545 $name = ucfirst( __FUNCTION__ );
552 'prop' =>
'categorieshtml',
555 $this->assertRegExp(
"#Category.*Category:$name.*$name#",
556 $res[0][
'parse'][
'categorieshtml'] );
557 $this->assertArrayNotHasKey(
'warnings',
$res[0] );
560 public function testEffectiveLangLinks() {
563 function ()
use ( &$hookRan ) {
570 'title' => __CLASS__,
571 'text' =>
'[[zh:' . __CLASS__ .
']]',
572 'effectivelanglinks' =>
'',
575 $this->assertTrue( $hookRan );
576 $this->assertSame(
'The parameter "effectivelanglinks" has been deprecated.',
577 $res[0][
'warnings'][
'parse'][
'warnings'] );
583 private function doTestLangLinks(
array $arr = [] ) {
584 $this->setupInterwiki();
588 'title' =>
'Omelette',
589 'text' =>
'[[madeuplanguage:Omelette]]',
590 'prop' =>
'langlinks',
593 $langLinks =
$res[0][
'parse'][
'langlinks'];
595 $this->assertCount( 1, $langLinks );
596 $this->assertSame(
'madeuplanguage', $langLinks[0][
'lang'] );
597 $this->assertSame(
'Omelette', $langLinks[0][
'title'] );
598 $this->assertSame(
'https://example.com/wiki/Omelette', $langLinks[0][
'url'] );
599 $this->assertArrayNotHasKey(
'warnings',
$res[0] );
602 public function testLangLinks() {
603 $this->doTestLangLinks();
606 public function testLangLinksWithSkin() {
608 $this->doTestLangLinks( [
'useskin' =>
'testing' ] );
611 public function testHeadItems() {
614 'title' => __CLASS__,
616 'prop' =>
'headitems',
619 $this->assertSame( [],
$res[0][
'parse'][
'headitems'] );
621 '"prop=headitems" is deprecated since MediaWiki 1.28. ' .
622 'Use "prop=headhtml" when creating new HTML documents, ' .
623 'or "prop=modules|jsconfigvars" when updating a document client-side.',
624 $res[0][
'warnings'][
'parse'][
'warnings']
628 public function testHeadItemsWithSkin() {
633 'title' => __CLASS__,
635 'prop' =>
'headitems',
636 'useskin' =>
'testing',
639 $this->assertSame( [],
$res[0][
'parse'][
'headitems'] );
641 '"prop=headitems" is deprecated since MediaWiki 1.28. ' .
642 'Use "prop=headhtml" when creating new HTML documents, ' .
643 'or "prop=modules|jsconfigvars" when updating a document client-side.',
644 $res[0][
'warnings'][
'parse'][
'warnings']
648 public function testModules() {
652 $output->addModules( [
'foo',
'bar' ] );
653 $output->addModuleStyles( [
'aaa',
'zzz' ] );
654 $output->addJsConfigVars( [
'x' =>
'y',
'z' => -3 ] );
659 'title' => __CLASS__,
661 'prop' =>
'modules|jsconfigvars|encodedjsconfigvars',
664 $this->assertSame( [
'foo',
'bar' ],
$res[0][
'parse'][
'modules'] );
665 $this->assertSame( [],
$res[0][
'parse'][
'modulescripts'] );
666 $this->assertSame( [
'aaa',
'zzz' ],
$res[0][
'parse'][
'modulestyles'] );
667 $this->assertSame( [
'x' =>
'y',
'z' => -3 ],
$res[0][
'parse'][
'jsconfigvars'] );
668 $this->assertSame(
'{"x":"y","z":-3}',
$res[0][
'parse'][
'encodedjsconfigvars'] );
669 $this->assertArrayNotHasKey(
'warnings',
$res[0] );
672 public function testModulesWithSkin() {
677 'pageid' => self::$pageId,
678 'useskin' =>
'testing',
682 [
'foo',
'bar',
'baz' ],
683 $res[0][
'parse'][
'modules'],
688 $res[0][
'parse'][
'modulescripts'],
689 'resp.parse.modulescripts'
692 [
'foo.styles',
'quux.styles' ],
693 $res[0][
'parse'][
'modulestyles'],
694 'resp.parse.modulestyles'
699 'Property "modules" was set but not "jsconfigvars" or ' .
700 '"encodedjsconfigvars". Configuration variables are necessary for ' .
701 'proper module usage.'
708 public function testIndicators() {
711 'title' => __CLASS__,
713 '<indicator name="b">BBB!</indicator>Some text<indicator name="a">aaa</indicator>',
714 'prop' =>
'indicators',
719 [
'b' =>
'BBB!',
'a' =>
'aaa' ],
720 $res[0][
'parse'][
'indicators']
722 $this->assertArrayNotHasKey(
'warnings',
$res[0] );
725 public function testIndicatorsWithSkin() {
730 'title' => __CLASS__,
732 '<indicator name="b">BBB!</indicator>Some text<indicator name="a">aaa</indicator>',
733 'prop' =>
'indicators',
734 'useskin' =>
'testing',
739 [
'a' =>
'aaa',
'b' =>
'BBB!' ],
740 $res[0][
'parse'][
'indicators']
742 $this->assertArrayNotHasKey(
'warnings',
$res[0] );
745 public function testIwlinks() {
746 $this->setupInterwiki();
750 'title' =>
'Omelette',
751 'text' =>
'[[:madeuplanguage:Omelette]][[madeuplanguage:Spaghetti]]',
755 $iwlinks =
$res[0][
'parse'][
'iwlinks'];
757 $this->assertCount( 1, $iwlinks );
758 $this->assertSame(
'madeuplanguage', $iwlinks[0][
'prefix'] );
759 $this->assertSame(
'https://example.com/wiki/Omelette', $iwlinks[0][
'url'] );
760 $this->assertSame(
'madeuplanguage:Omelette', $iwlinks[0][
'title'] );
761 $this->assertArrayNotHasKey(
'warnings',
$res[0] );
764 public function testLimitReports() {
767 'pageid' => self::$pageId,
768 'prop' =>
'limitreportdata|limitreporthtml',
772 $this->assertInternalType(
'array',
$res[0][
'parse'][
'limitreportdata'] );
773 $this->assertInternalType(
'string',
$res[0][
'parse'][
'limitreporthtml'] );
774 $this->assertArrayNotHasKey(
'warnings',
$res[0] );
777 public function testParseTreeNonWikitext() {
779 '"prop=parsetree" is only supported for wikitext content.' );
784 'contentmodel' =>
'json',
785 'prop' =>
'parsetree',
789 public function testParseTree() {
792 'text' =>
"Some ''text'' is {{nice|to have|i=think}}",
793 'contentmodel' =>
'wikitext',
794 'prop' =>
'parsetree',
800 '#^<root>Some \'\'text\'\' is <template><title>nice</title>' .
801 '<part><name index="1"/><value>to have</value></part>' .
802 '<part><name>i</name>(?:<equals>)?=(?:</equals>)?<value>think</value></part>' .
803 '</template></root>$#',
804 $res[0][
'parse'][
'parsetree']
806 $this->assertArrayNotHasKey(
'warnings',
$res[0] );
809 public function testDisableTidy() {
810 $this->
setMwGlobals(
'wgTidyConfig', [
'driver' =>
'RemexHtml' ] );
816 'text' =>
"<b>Mixed <i>up</b></i>",
817 'contentmodel' =>
'wikitext',
819 $this->assertParsedTo(
"<p><b>Mixed <i>up</i></b>\n</p>", $res1 );
823 'text' =>
"<b>Mixed <i>up</b></i>",
824 'contentmodel' =>
'wikitext',
828 $this->assertParsedTo(
"<p><b>Mixed <i>up</b></i>\n</p>", $res2,
829 'The parameter "disabletidy" has been deprecated.' );
832 public function testFormatCategories() {
833 $name = ucfirst( __FUNCTION__ );
835 $this->
editPage(
"Category:$name",
'Content' );
836 $this->
editPage(
'Category:Hidden',
'__HIDDENCAT__' );
840 'title' => __CLASS__,
841 'text' =>
"[[Category:$name]][[Category:Foo|Sort me]][[Category:Hidden]]",
842 'prop' =>
'categories',
846 [ [
'sortkey' =>
'',
'category' =>
$name ],
847 [
'sortkey' =>
'Sort me',
'category' =>
'Foo',
'missing' =>
true ],
848 [
'sortkey' =>
'',
'category' =>
'Hidden',
'hidden' =>
true ] ],
849 $res[0][
'parse'][
'categories']
851 $this->assertArrayNotHasKey(
'warnings',
$res[0] );