wiki.techinc.nl/tests/phpunit/includes/parser/ParserMethodsTest.php

436 lines
13 KiB
PHP
Raw Normal View History

<?php
use MediaWiki\Language\RawMessage;
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\MutableRevisionRecord;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\User\UserIdentityValue;
/**
* @group Database
* @covers Parser
* @covers BlockLevelPass
*/
class ParserMethodsTest extends MediaWikiLangTestCase {
use MockTitleTrait;
Clean and repair many phpunit tests (+ fix implied configuration) This commit depends on the introduction of MediaWikiTestCase::setMwGlobals in change Iccf6ea81f4. Various tests already set their globals, but forgot to restore them afterwards, or forgot to call the parent setUp, tearDown... Either way they won't have to anymore with setMwGlobals. Consistent use of function characteristics: * protected function setUp * protected function tearDown * public static function (provide..) (Matching the function signature with PHPUnit/Framework/TestCase.php) Replaces: * public function (setUp|tearDown)\( * protected function $1( * \tfunction (setUp|tearDown)\( * \tprotected function $1( * \tfunction (data|provide)\( * \tpublic static function $1\( Also renamed a few "data#", "provider#" and "provides#" functions to "provide#" for consistency. This also removes confusion where the /media tests had a few private methods called dataFile(), which were sometimes expected to be data providers. Fixes: TimestampTest often failed due to a previous test setting a different language (it tests "1 hour ago" so need to make sure it is set to English). MWNamespaceTest became a lot cleaner now that it executes with a known context. Though the now-redundant code that was removed didn't work anyway because wgContentNamespaces isn't keyed by namespace id, it had them was values... FileBackendTest: * Fixed: "PHP Fatal: Using $this when not in object context" HttpTest * Added comment about: "PHP Fatal: Call to protected MWHttpRequest::__construct()" (too much unrelated code to fix in this commit) ExternalStoreTest * Add an assertTrue as well, without it the test is useless because regardless of whether wgExternalStores is true or false it only uses it if it is an array. Change-Id: I9d2b148e57bada64afeb7d5a99bec0e58f8e1561
2012-10-08 10:56:20 +00:00
public static function providePreSaveTransform() {
return [
[ 'hello this is ~~~',
"hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
],
[ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
],
];
}
/**
Clean and repair many phpunit tests (+ fix implied configuration) This commit depends on the introduction of MediaWikiTestCase::setMwGlobals in change Iccf6ea81f4. Various tests already set their globals, but forgot to restore them afterwards, or forgot to call the parent setUp, tearDown... Either way they won't have to anymore with setMwGlobals. Consistent use of function characteristics: * protected function setUp * protected function tearDown * public static function (provide..) (Matching the function signature with PHPUnit/Framework/TestCase.php) Replaces: * public function (setUp|tearDown)\( * protected function $1( * \tfunction (setUp|tearDown)\( * \tprotected function $1( * \tfunction (data|provide)\( * \tpublic static function $1\( Also renamed a few "data#", "provider#" and "provides#" functions to "provide#" for consistency. This also removes confusion where the /media tests had a few private methods called dataFile(), which were sometimes expected to be data providers. Fixes: TimestampTest often failed due to a previous test setting a different language (it tests "1 hour ago" so need to make sure it is set to English). MWNamespaceTest became a lot cleaner now that it executes with a known context. Though the now-redundant code that was removed didn't work anyway because wgContentNamespaces isn't keyed by namespace id, it had them was values... FileBackendTest: * Fixed: "PHP Fatal: Using $this when not in object context" HttpTest * Added comment about: "PHP Fatal: Call to protected MWHttpRequest::__construct()" (too much unrelated code to fix in this commit) ExternalStoreTest * Add an assertTrue as well, without it the test is useless because regardless of whether wgExternalStores is true or false it only uses it if it is an array. Change-Id: I9d2b148e57bada64afeb7d5a99bec0e58f8e1561
2012-10-08 10:56:20 +00:00
* @dataProvider providePreSaveTransform
*/
public function testPreSaveTransform( $text, $expected ) {
$title = Title::makeTitle( NS_MAIN, 'TestPreSaveTransform' );
$user = new User();
$user->setName( "127.0.0.1" );
$popts = ParserOptions::newFromUser( $user );
$text = $this->getServiceContainer()->getParser()
->preSaveTransform( $text, $title, $user, $popts );
$this->assertEquals( $expected, $text );
}
public static function provideStripOuterParagraph() {
// This mimics the most common use case (stripping paragraphs generated by the parser).
$message = new RawMessage( "Message text." );
return [
[
"<p>Text.</p>",
"Text.",
],
[
"<p class='foo'>Text.</p>",
"<p class='foo'>Text.</p>",
],
[
"<p>Text.\n</p>\n",
"Text.",
],
[
"<p>Text.</p><p>More text.</p>",
"<p>Text.</p><p>More text.</p>",
],
[
$message->parse(),
"Message text.",
],
];
}
/**
* @dataProvider provideStripOuterParagraph
*/
public function testStripOuterParagraph( $text, $expected ) {
$this->assertEquals( $expected, Parser::stripOuterParagraph( $text ) );
}
Add markup to page titles to distinguish the namespace and the main text Pages outside of the main namespace now have the following markup in their <h1> page titles, using 'Talk:Hello' as an example: <h1> <span class="mw-page-title-namespace">Talk</span> <span class="mw-page-title-separator">:</span> <span class="mw-page-title-main">Hello</span> </h1> (line breaks and spaces added for readability) Pages in the main namespace only have the last part, e.g. for 'Hello': <h1> <span class="mw-page-title-main">Hello</span> </h1> The change is motivated by a desire to style the titles differently on talk pages in the DiscussionTools extension (T313636), but it could also be used for other things: * Language-specific tweaks (e.g. adding typographically-correct spaces around the colon separator: T249149, or replacing it with a different character: T36295) * Site-specific tweaks (e.g. de-emphasize or emphasize specific namespaces like 'Draft': T62973 / T236215) The markup is also added to automatically language-converted titles. It is not added when the title is overridden using the wikitext `{{DISPLAYTITLE:…}}` or `-{T|…}-` forms. I think this is a small limitation, as those forms mostly used in the main namespace, where the extra markup isn't very helpful anyway. This may be improved in the future. As a workaround, users could also just add the same HTML markup to their wikitext (as those forms accept it). It is not also added when the title is overridden by an extension like Translate. Maybe we'll have a better API before anyone wants to do that. If not, one could un-mark Parser::formatPageTitle() as @internal, and use that method to add the markup themselves. Bug: T306440 Change-Id: I62b17ef22de3606d736e6c261e542a34b58b5a05
2022-08-09 02:52:53 +00:00
public static function provideFormatPageTitle() {
return [
"Non-main namespace" => [
[ 'Talk', ':', 'Hello' ],
'<span class="mw-page-title-namespace">Talk</span><span class="mw-page-title-separator">:</span><span class="mw-page-title-main">Hello</span>',
],
"Main namespace (ignores the separator)" => [
[ '', ':', 'Hello' ],
'<span class="mw-page-title-main">Hello</span>',
],
"Pieces are HTML-escaped" => [
[ 'Ta&lk', ':', 'He&llo' ],
'<span class="mw-page-title-namespace">Ta&amp;lk</span><span class="mw-page-title-separator">:</span><span class="mw-page-title-main">He&amp;llo</span>',
],
"In the future, the colon separator could be localized" => [
[ 'Talk', ' : ', 'Hello' ],
'<span class="mw-page-title-namespace">Talk</span><span class="mw-page-title-separator"> : </span><span class="mw-page-title-main">Hello</span>',
],
"In the future, displaytitle could be customized separately from the namespace" => [
[ 'Talk', ':', new HtmlArmor( '<span class="whatever">Hello</span>' ) ],
'<span class="mw-page-title-namespace">Talk</span><span class="mw-page-title-separator">:</span><span class="mw-page-title-main"><span class="whatever">Hello</span></span>',
],
];
}
/**
* @dataProvider provideFormatPageTitle
*/
public function testFormatPageTitle( $args, $expected ) {
$this->assertEquals( $expected, Parser::formatPageTitle( ...$args ) );
}
public function testRecursiveParse() {
$title = Title::makeTitle( NS_MAIN, 'Foo' );
$parser = $this->getServiceContainer()->getParser();
$po = ParserOptions::newFromAnon();
$parser->setHook( 'recursivecallparser', [ $this, 'helperParserFunc' ] );
$this->expectException( MWException::class );
$this->expectExceptionMessage(
"Parser state cleared while parsing. Did you call Parser::parse recursively?"
);
$parser->parse( '<recursivecallparser>baz</recursivecallparser>', $title, $po );
}
public function helperParserFunc( $input, $args, $parser ) {
$title = Title::makeTitle( NS_MAIN, 'Foo' );
$po = ParserOptions::newFromAnon();
$parser->parse( $input, $title, $po );
return 'bar';
}
public function testCallParserFunction() {
// Normal parses test passing PPNodes. Test passing an array.
$title = Title::makeTitle( NS_MAIN, 'TestCallParserFunction' );
$parser = $this->getServiceContainer()->getParser();
$parser->startExternalParse(
$title,
ParserOptions::newFromAnon(),
Parser::OT_HTML
);
$frame = $parser->getPreprocessor()->newFrame();
$ret = $parser->callParserFunction( $frame, '#tag',
[ 'pre', 'foo', 'style' => 'margin-left: 1.6em' ]
);
Make Parser::$mStripState private This property was deprecated in 1.35. The replacement function Parser::getStripState() was introduced in MediaWiki 1.34. Code search: https://codesearch.wmcloud.org/search/?q=-%3EmStripState&i=nope&files=&excludeFiles=&repos= Depends-On clauses below are for WMF-deployed code. Other uses in non-WMF-deployed code have been patched in: * https://github.com/SemanticMediaWiki/SemanticMediaWiki/pull/4936 * Idc2fadf5105d6eb30777a16dff0035bceff17174 (BlueSpiceSocial) * I130fd61a8fe2d28e6b116a3fcc767b8abd466cea (ContributionScores) * I3676fe9882ce9de5732cb7230528134df544ff98 (HierarchyBuilder) * Ic392afd1e93ae0003fd0ab65114ec1ff38bb2927 (Mpdf) * I4b01017da752def982777c4fea5fad5e21e4c7ea (MsLinks) * I09726078ee62eb99e032b8faa5f938e20107f48c (Negref) * Ibfd6b7064a8e650c3492e0d2764d4f7afc4937bf (PageForms) * Ia865435688d36178508f21cffae79538c919035c (PageInCat) * Ib94db0e6d365e4cb3f51121340a04d31b88add62 (ParserFun) * I8660c0691b7e9842106d7dcb224ff5ecf374e4bc (PhpTags) * I1ad5f78e5a937767123400ceca4967941e256e5e (RandomImageByCategory) * I4539e7cea597f71b2a2d9a6cae137bc25085ed6b (ReplaceSet) * If8ff2e21952b3f08d3a8950d42e2afb56973fb89 (SemanticDrilldown) * I4a5bd64760cdde5b614a7d4e2b09e8d0634b2056 (SemanticPageSeries) * Ia04f1aac1d8ae4ea16c98cfbbe72195fffe653b6 (SemanticRating) * Id2a2e2d024922e3babf756ebae1a4f59b4358146 (Spark) * I4a979024b18ec4834dc06b51ee0f018d749c6dab (Tooltip) * Iaf179914863998b32bfecc16c874c3cffd6c26e9 (VIKI) * I2de0e7a6c133c2e1f3cb7502a81d809c4489db4c (XSL) * https://gitlab.com/hydrawiki/extensions/characterescapes/-/merge_requests/1 (characterescapes) * https://github.com/JeroenDeDauw/Validator/pull/38 (Validator) * https://github.com/lingua-libre/CustomSubtitle/pull/3 (CustomSubtitle) * https://github.com/mkroetzsch/AutoCreatePage/pull/12 (AutoCreatePage) * https://gitlab.com/nonsensopedia/extensions/advancedbacklinks/-/merge_requests/95 (advancedbacklinks) * https://github.com/vonloxley/Shariff-Mediawiki/pull/16 (Shariff-Mediawiki) Bug: T275160 Depends-On: I062ac8b69756a7ad35d8cc744b4735fd2e70f13e Depends-On: Ic4be2bad176f2c59a1104219be10045cd5929261 Depends-On: I3cb117a91c8c57331b6b513f64ddb68d6ae2758c Depends-On: I67b5926f2f851b3dc709d044eec5dd3df5065482 Depends-On: I7806068e1cd6e4da66adfe7bb75095d4bfb5d6bc Depends-On: I429da35ca4e276c852b8d6ee102ff19f742c22c0 Change-Id: I4af85a46cfcafba15aa5ee50fda9f7b04681d6e6
2021-02-19 22:01:19 +00:00
$ret['text'] = $parser->getStripState()->unstripBoth( $ret['text'] );
$this->assertSame( [
'found' => true,
'text' => '<pre style="margin-left: 1.6em">foo</pre>',
], $ret, 'callParserFunction works for {{#tag:pre|foo|style=margin-left: 1.6em}}' );
}
/**
* @covers Parser
* @covers ParserOutput::getSections
*/
public function testGetSections() {
$this->overrideConfigValue( MainConfigNames::FragmentMode, [ 'html5' ] );
$title = Title::makeTitle( NS_MAIN, 'TestGetSections' );
$out = $this->getServiceContainer()->getParser()->parse(
"==foo==\n<h2>bar</h2>\n==baz==\n== Romeo+Juliet %A Ó %20 ==\ntest",
$title,
ParserOptions::newFromAnon()
);
$this->assertSame( [
[
'toclevel' => 1,
'level' => '2',
'line' => 'foo',
'number' => '1',
'index' => '1',
'fromtitle' => $title->getPrefixedDBkey(),
'byteoffset' => 0,
'anchor' => 'foo',
'linkAnchor' => 'foo',
],
[
'toclevel' => 1,
'level' => '2',
'line' => 'bar',
'number' => '2',
'index' => '',
'fromtitle' => false,
'byteoffset' => null,
'anchor' => 'bar',
'linkAnchor' => 'bar',
],
[
'toclevel' => 1,
'level' => '2',
'line' => 'baz',
'number' => '3',
'index' => '2',
'fromtitle' => $title->getPrefixedDBkey(),
'byteoffset' => 21,
'anchor' => 'baz',
'linkAnchor' => 'baz',
],
[
'toclevel' => 1,
'level' => '2',
'line' => 'Romeo+Juliet %A Ó %20',
'number' => '4',
'index' => '3',
'fromtitle' => $title->getPrefixedDBkey(),
'byteoffset' => 29,
'anchor' => 'Romeo+Juliet_%A_Ó_%20',
'linkAnchor' => 'Romeo+Juliet_%A_Ó_%2520',
]
], $out->getSections(), 'getSections() with proper value when <h2> is used' );
}
/**
* @dataProvider provideNormalizeLinkUrl
*/
public function testNormalizeLinkUrl( $explanation, $url, $expected ) {
$this->assertEquals( $expected, Parser::normalizeLinkUrl( $url ), $explanation );
}
public static function provideNormalizeLinkUrl() {
return [
[
'Escaping of unsafe characters',
'http://example.org/foo bar?param[]="value"&param[]=valüe',
'http://example.org/foo%20bar?param%5B%5D=%22value%22&param%5B%5D=val%C3%BCe',
],
[
'Case normalization of percent-encoded characters',
'http://example.org/%ab%cD%Ef%FF',
'http://example.org/%AB%CD%EF%FF',
],
[
'Unescaping of safe characters',
'http://example.org/%3C%66%6f%6F%3E?%3C%66%6f%6F%3E#%3C%66%6f%6F%3E',
'http://example.org/%3Cfoo%3E?%3Cfoo%3E#%3Cfoo%3E',
],
[
'Context-sensitive replacement of sometimes-safe characters',
'http://example.org/%23%2F%3F%26%3D%2B%3B?%23%2F%3F%26%3D%2B%3B#%23%2F%3F%26%3D%2B%3B',
'http://example.org/%23%2F%3F&=+;?%23/?%26%3D%2B%3B#%23/?&=+;',
],
[
'IPv6 links aren\'t escaped',
'http://[::1]/foobar',
'http://[::1]/foobar',
],
[
'non-IPv6 links aren\'t unescaped',
'http://%5B::1%5D/foobar',
'http://%5B::1%5D/foobar',
],
];
}
public function testWrapOutput() {
$title = Title::makeTitle( NS_MAIN, 'Foo' );
$po = ParserOptions::newFromAnon();
$parser = $this->getServiceContainer()->getParser();
$parser->parse( 'Hello World', $title, $po );
$text = $parser->getOutput()->getText();
$this->assertStringContainsString( 'Hello World', $text );
$this->assertStringContainsString( '<div', $text );
$this->assertStringContainsString( 'class="mw-parser-output"', $text );
}
public function provideRevisionAccess() {
$title = $this->makeMockTitle( 'ParserRevisionAccessTest', [
'language' => MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' )
] );
$frank = new UserIdentityValue( 5, 'Frank' );
$text = '* user:{{REVISIONUSER}};id:{{REVISIONID}};time:{{REVISIONTIMESTAMP}};';
$po = new ParserOptions( $frank );
yield 'current' => [ $text, $po, 0, 'user:CurrentAuthor;id:200;time:20160606000000;' ];
yield 'anonymous' => [ $text, $po, null, 'user:;id:;time:' ];
yield 'current with ID' => [ $text, $po, 200, 'user:CurrentAuthor;id:200;time:20160606000000;' ];
$text = '* user:{{REVISIONUSER}};id:{{REVISIONID}};time:{{REVISIONTIMESTAMP}};';
$po = new ParserOptions( $frank );
yield 'old' => [ $text, $po, 100, 'user:OldAuthor;id:100;time:20140404000000;' ];
$oldRevision = new MutableRevisionRecord( $title );
$oldRevision->setId( 100 );
$oldRevision->setUser( new UserIdentityValue( 7, 'FauxAuthor' ) );
$oldRevision->setTimestamp( '20141111111111' );
$oldRevision->setContent( SlotRecord::MAIN, new WikitextContent( 'FAUX' ) );
$po = new ParserOptions( $frank );
$po->setCurrentRevisionRecordCallback( static function () use ( $oldRevision ) {
return $oldRevision;
} );
yield 'old with override' => [ $text, $po, 100, 'user:FauxAuthor;id:100;time:20141111111111;' ];
$text = '* user:{{REVISIONUSER}};user-subst:{{subst:REVISIONUSER}};';
$po = new ParserOptions( $frank );
$po->setIsPreview( true );
yield 'preview without override, using context' => [
$text,
$po,
null,
'user:Frank;',
'user-subst:Frank;',
];
$text = '* user:{{REVISIONUSER}};time:{{REVISIONTIMESTAMP}};'
. 'user-subst:{{subst:REVISIONUSER}};time-subst:{{subst:REVISIONTIMESTAMP}};';
$newRevision = new MutableRevisionRecord( $title );
$newRevision->setUser( new UserIdentityValue( 9, 'NewAuthor' ) );
$newRevision->setTimestamp( '20180808000000' );
$newRevision->setContent( SlotRecord::MAIN, new WikitextContent( 'NEW' ) );
$po = new ParserOptions( $frank );
$po->setIsPreview( true );
$po->setCurrentRevisionRecordCallback( static function () use ( $newRevision ) {
return $newRevision;
} );
yield 'preview' => [
$text,
$po,
null,
'user:NewAuthor;time:20180808000000;',
'user-subst:NewAuthor;time-subst:20180808000000;',
];
$po = new ParserOptions( $frank );
$po->setCurrentRevisionRecordCallback( static function () use ( $newRevision ) {
return $newRevision;
} );
yield 'pre-save' => [
$text,
$po,
null,
'user:NewAuthor;time:20180808000000;',
'user-subst:NewAuthor;time-subst:20180808000000;',
];
$text = "(ONE)<includeonly>(TWO)</includeonly>"
. "<noinclude>#{{:ParserRevisionAccessTest}}#</noinclude>";
$newRevision = new MutableRevisionRecord( $title );
$newRevision->setUser( new UserIdentityValue( 9, 'NewAuthor' ) );
$newRevision->setTimestamp( '20180808000000' );
$newRevision->setContent( SlotRecord::MAIN, new WikitextContent( $text ) );
$po = new ParserOptions( $frank );
$po->setIsPreview( true );
$po->setCurrentRevisionRecordCallback( static function () use ( $newRevision ) {
return $newRevision;
} );
yield 'preview with self-transclude' => [ $text, $po, null, '(ONE)#(ONE)(TWO)#' ];
}
/**
* @dataProvider provideRevisionAccess
*/
public function testRevisionAccess(
$text,
ParserOptions $po,
$revId,
$expectedInHtml,
$expectedInPst = null
) {
$title = $this->makeMockTitle( 'ParserRevisionAccessTest', [
'language' => $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' )
] );
$oldRevision = new MutableRevisionRecord( $title );
$oldRevision->setId( 100 );
$oldRevision->setUser( new UserIdentityValue( 7, 'OldAuthor' ) );
$oldRevision->setTimestamp( '20140404000000' );
$oldRevision->setContent( SlotRecord::MAIN, new WikitextContent( 'OLD' ) );
$currentRevision = new MutableRevisionRecord( $title );
$currentRevision->setId( 200 );
$currentRevision->setUser( new UserIdentityValue( 9, 'CurrentAuthor' ) );
$currentRevision->setTimestamp( '20160606000000' );
$currentRevision->setContent( SlotRecord::MAIN, new WikitextContent( 'CURRENT' ) );
$revisionStore = $this->createMock( RevisionStore::class );
$revisionStore
->method( 'getKnownCurrentRevision' )
->willReturnMap( [
[ $title, 100, $oldRevision ],
[ $title, 200, $currentRevision ],
[ $title, 0, $currentRevision ],
] );
$revisionStore
->method( 'getRevisionById' )
->willReturnMap( [
[ 100, 0, null, $oldRevision ],
[ 200, 0, null, $currentRevision ],
] );
$this->setService( 'RevisionStore', $revisionStore );
$parser = $this->getServiceContainer()->getParser();
$parser->parse( $text, $title, $po, true, true, $revId );
$html = $parser->getOutput()->getText();
$this->assertStringContainsString( $expectedInHtml, $html, 'In HTML' );
if ( $expectedInPst !== null ) {
$pst = $parser->preSaveTransform( $text, $title, $po->getUserIdentity(), $po );
$this->assertStringContainsString( $expectedInPst, $pst, 'After Pre-Safe Transform' );
}
}
public static function provideGuessSectionNameFromWikiText() {
return [
[ '1/2', 'html5', '#1/2' ],
[ '1/2', 'legacy', '#1.2F2' ],
];
}
/** @dataProvider provideGuessSectionNameFromWikiText */
public function testGuessSectionNameFromWikiText( $input, $mode, $expected ) {
$this->overrideConfigValue( MainConfigNames::FragmentMode, [ $mode ] );
$result = $this->getServiceContainer()->getParser()
->guessSectionNameFromWikiText( $input );
$this->assertEquals( $expected, $result );
}
// @todo Add tests for cleanSig() / cleanSigInSig(), getSection(),
// replaceSection(), getPreloadText()
}