+ public function testTitleProvided() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => 'Some interesting page',
+ 'text' => '{{PAGENAME}} has attracted my attention',
+ ] );
+
+ $this->assertParsedTo( "<p>Some interesting page has attracted my attention\n</p>", $res );
+ }
+
+ public function testSection() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name,
+ "Intro\n\n== Section 1 ==\n\nContent 1\n\n== Section 2 ==\n\nContent 2" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'section' => 1,
+ ] );
+
+ $this->assertParsedToRegExp( '!<h2>.*Section 1.*</h2>\n<p>Content 1\n</p>!', $res );
+ }
+
+ public function testInvalidSection() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The "section" parameter must be a valid section ID or "new".' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'section' => 'T-new',
+ ] );
+ }
+
+ public function testSectionNoContent() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $status = $this->editPage( $name,
+ "Intro\n\n== Section 1 ==\n\nContent 1\n\n== Section 2 ==\n\nContent 2" );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "Missing content for page ID {$status->value['revision']->getPage()}." );
+
+ $this->db->delete( 'revision', [ 'rev_id' => $status->value['revision']->getId() ] );
+
+ // Suppress warning in WikiPage::getContentModel
+ Wikimedia\suppressWarnings();
+ try {
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'section' => 1,
+ ] );
+ } finally {
+ Wikimedia\restoreWarnings();
+ }
+ }
+
+ public function testNewSectionWithPage() {
+ $this->setExpectedException( ApiUsageException::class,
+ '"section=new" cannot be combined with the "oldid", "pageid" or "page" ' .
+ 'parameters. Please use "title" and "text".' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => __CLASS__,
+ 'section' => 'new',
+ ] );
+ }
+
+ public function testNonexistentOldId() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'There is no revision with ID 2147483647.' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'oldid' => pow( 2, 31 ) - 1,
+ ] );
+ }
+
+ public function testUnfollowedRedirect() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "#REDIRECT [[$name 2]]" );
+ $this->editPage( "$name 2", "Some ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ ] );
+
+ // Can't use assertParsedTo because the parser output is different for
+ // redirects
+ $this->assertRegExp( "/Redirect to:.*$name 2/", $res[0]['parse']['text'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testFollowedRedirect() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "#REDIRECT [[$name 2]]" );
+ $this->editPage( "$name 2", "Some ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'redirects' => true,
+ ] );
+
+ $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res );
+ }
+
+ public function testFollowedRedirectById() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $id = $this->editPage( $name, "#REDIRECT [[$name 2]]" )->value['revision']->getPage();
+ $this->editPage( "$name 2", "Some ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'pageid' => $id,
+ 'redirects' => true,
+ ] );
+
+ $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res );
+ }
+
+ public function testInvalidTitle() {
+ $this->setExpectedException( ApiUsageException::class, 'Bad title "|".' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => '|',
+ ] );
+ }
+
+ public function testTitleWithNonexistentRevId() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'There is no revision with ID 2147483647.' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'revid' => pow( 2, 31 ) - 1,
+ ] );
+ }
+
+ public function testTitleWithNonMatchingRevId() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => $name,
+ 'revid' => self::$revIds['latest'],
+ 'text' => 'Some text',
+ ] );
+
+ $this->assertParsedTo( "<p>Some text\n</p>", $res,
+ 'r' . self::$revIds['latest'] . " is not a revision of $name." );
+ }
+
+ public function testRevId() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'revid' => self::$revIds['latest'],
+ 'text' => 'My revid is {{REVISIONID}}!',
+ ] );
+
+ $this->assertParsedTo( "<p>My revid is " . self::$revIds['latest'] . "!\n</p>", $res );
+ }
+
+ public function testTitleNoText() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => 'Special:AllPages',
+ ] );
+
+ $this->assertParsedTo( '', $res,
+ '"title" used without "text", and parsed page properties were requested. ' .
+ 'Did you mean to use "page" instead of "title"?' );
+ }
+
+ public function testRevidNoText() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'revid' => self::$revIds['latest'],
+ ] );
+
+ $this->assertParsedTo( '', $res,
+ '"revid" used without "text", and parsed page properties were requested. ' .
+ 'Did you mean to use "oldid" instead of "revid"?' );
+ }
+
+ public function testTextNoContentModel() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "Some ''text''",
+ ] );
+
+ $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res,
+ 'No "title" or "contentmodel" was given, assuming wikitext.' );
+ }
+
+ public function testSerializationError() {
+ $this->setExpectedException( APIUsageException::class,
+ 'Content serialization failed: Could not unserialize content' );
+
+ $this->mergeMwGlobalArrayValue( 'wgContentHandlers',
+ [ 'testing-serialize-error' => 'DummySerializeErrorContentHandler' ] );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "Some ''text''",
+ 'contentmodel' => 'testing-serialize-error',
+ ] );
+ }
+
+ public function testNewSection() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'section' => 'new',
+ 'sectiontitle' => 'Title',
+ 'text' => 'Content',
+ ] );
+
+ $this->assertParsedToRegExp( '!<h2>.*Title.*</h2>\n<p>Content\n</p>!', $res );
+ }
+
+ public function testExistingSection() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'section' => 1,
+ 'text' => "Intro\n\n== Section 1 ==\n\nContent\n\n== Section 2 ==\n\nMore content",
+ ] );
+
+ $this->assertParsedToRegExp( '!<h2>.*Section 1.*</h2>\n<p>Content\n</p>!', $res );
+ }
+
+ public function testNoPst() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Template:$name", "Template ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "{{subst:$name}}",
+ 'contentmodel' => 'wikitext',
+ ] );
+
+ $this->assertParsedTo( "<p>{{subst:$name}}\n</p>", $res );
+ }
+
+ public function testPst() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Template:$name", "Template ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'pst' => '',
+ 'text' => "{{subst:$name}}",
+ 'contentmodel' => 'wikitext',
+ 'prop' => 'text|wikitext',
+ ] );
+
+ $this->assertParsedTo( "<p>Template <i>text</i>\n</p>", $res );
+ $this->assertSame( "{{subst:$name}}", $res[0]['parse']['wikitext'] );
+ }
+
+ public function testOnlyPst() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Template:$name", "Template ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'onlypst' => '',
+ 'text' => "{{subst:$name}}",
+ 'contentmodel' => 'wikitext',
+ 'prop' => 'text|wikitext',
+ 'summary' => 'Summary',
+ ] );
+
+ $this->assertSame(
+ [ 'parse' => [
+ 'text' => "Template ''text''",
+ 'wikitext' => "{{subst:$name}}",
+ 'parsedsummary' => 'Summary',
+ ] ],
+ $res[0]
+ );
+ }
+
+ public function testHeadHtml() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => __CLASS__,
+ 'prop' => 'headhtml',
+ ] );
+
+ // Just do a rough sanity check
+ $this->assertRegExp( '#<!DOCTYPE.*<html.*<head.*</head>.*<body#s',
+ $res[0]['parse']['headhtml'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testCategoriesHtml() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "[[Category:$name]]" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'prop' => 'categorieshtml',
+ ] );
+
+ $this->assertRegExp( "#Category.*Category:$name.*$name#",
+ $res[0]['parse']['categorieshtml'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testEffectiveLangLinks() {
+ $hookRan = false;
+ $this->setTemporaryHook( 'LanguageLinks',
+ function () use ( &$hookRan ) {
+ $hookRan = true;
+ }
+ );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => '[[zh:' . __CLASS__ . ']]',
+ 'effectivelanglinks' => '',
+ ] );
+
+ $this->assertTrue( $hookRan );
+ $this->assertSame( 'The parameter "effectivelanglinks" has been deprecated.',
+ $res[0]['warnings']['parse']['warnings'] );
+ }
+
+ /**
+ * @param array $arr Extra params to add to API request
+ */
+ private function doTestLangLinks( array $arr = [] ) {
+ $this->setupInterwiki();
+
+ $res = $this->doApiRequest( array_merge( [
+ 'action' => 'parse',
+ 'title' => 'Omelette',
+ 'text' => '[[madeuplanguage:Omelette]]',
+ 'prop' => 'langlinks',
+ ], $arr ) );
+
+ $langLinks = $res[0]['parse']['langlinks'];
+
+ $this->assertCount( 1, $langLinks );
+ $this->assertSame( 'madeuplanguage', $langLinks[0]['lang'] );
+ $this->assertSame( 'Omelette', $langLinks[0]['title'] );
+ $this->assertSame( 'https://example.com/wiki/Omelette', $langLinks[0]['url'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testLangLinks() {
+ $this->doTestLangLinks();
+ }
+
+ public function testLangLinksWithSkin() {
+ $this->setupSkin();
+ $this->doTestLangLinks( [ 'useskin' => 'testing' ] );
+ }
+
+ public function testHeadItems() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => '',
+ 'prop' => 'headitems',
+ ] );
+
+ $this->assertSame( [], $res[0]['parse']['headitems'] );
+ $this->assertSame(
+ '"prop=headitems" is deprecated since MediaWiki 1.28. ' .
+ 'Use "prop=headhtml" when creating new HTML documents, ' .
+ 'or "prop=modules|jsconfigvars" when updating a document client-side.',
+ $res[0]['warnings']['parse']['warnings']
+ );
+ }
+
+ public function testHeadItemsWithSkin() {
+ $this->setupSkin();
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => '',
+ 'prop' => 'headitems',
+ 'useskin' => 'testing',
+ ] );
+
+ $this->assertSame( [], $res[0]['parse']['headitems'] );
+ $this->assertSame(
+ '"prop=headitems" is deprecated since MediaWiki 1.28. ' .
+ 'Use "prop=headhtml" when creating new HTML documents, ' .
+ 'or "prop=modules|jsconfigvars" when updating a document client-side.',
+ $res[0]['warnings']['parse']['warnings']
+ );
+ }
+
+ public function testModules() {
+ $this->setTemporaryHook( 'ParserAfterParse',
+ function ( $parser ) {
+ $output = $parser->getOutput();
+ $output->addModules( [ 'foo', 'bar' ] );
+ $output->addModuleScripts( [ 'baz', 'quuz' ] );
+ $output->addModuleStyles( [ 'aaa', 'zzz' ] );
+ $output->addJsConfigVars( [ 'x' => 'y', 'z' => -3 ] );
+ }
+ );
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => 'Content',
+ 'prop' => 'modules|jsconfigvars|encodedjsconfigvars',
+ ] );
+
+ $this->assertSame( [ 'foo', 'bar' ], $res[0]['parse']['modules'] );
+ $this->assertSame( [ 'baz', 'quuz' ], $res[0]['parse']['modulescripts'] );
+ $this->assertSame( [ 'aaa', 'zzz' ], $res[0]['parse']['modulestyles'] );
+ $this->assertSame( [ 'x' => 'y', 'z' => -3 ], $res[0]['parse']['jsconfigvars'] );
+ $this->assertSame( '{"x":"y","z":-3}', $res[0]['parse']['encodedjsconfigvars'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testModulesWithSkin() {
+ $this->setupSkin();