overrideConfigValue( MainConfigNames::ArticlePath, '/wiki/$1' );
// We'd also test the warning, but injecting a mock logger into a static method is tricky.
if ( !$userName ) {
$actual = @Linker::userLink( $userId, $userName, $altUserName );
} else {
$actual = Linker::userLink( $userId, $userName, $altUserName );
}
$this->assertEquals( $expected, $actual, $msg );
}
public static function provideCasesForUserLink() {
# Format:
# - expected
# - userid
# - username
# - optional altUserName
# - optional message
return [
# Empty name (T222529)
'Empty username, userid 0' => [ '(no username available)', 0, '' ],
'Empty username, userid > 0' => [ '(no username available)', 73, '' ],
'false instead of username' => [ '(no username available)', 73, false ],
'null instead of username' => [ '(no username available)', 0, null ],
# ## ANONYMOUS USER ########################################
[
'JohnDoe',
0, 'JohnDoe', false,
],
[
'::1',
0, '::1', false,
'Anonymous with pretty IPv6'
],
[
'::1',
0, '0:0:0:0:0:0:0:1', false,
'Anonymous with almost pretty IPv6'
],
[
'::1',
0, '0000:0000:0000:0000:0000:0000:0000:0001', false,
'Anonymous with full IPv6'
],
[
'AlternativeUsername',
0, '::1', 'AlternativeUsername',
'Anonymous with pretty IPv6 and an alternative username'
],
# IPV4
[
'127.0.0.1',
0, '127.0.0.1', false,
'Anonymous with IPv4'
],
[
'AlternativeUsername',
0, '127.0.0.1', 'AlternativeUsername',
'Anonymous with IPv4 and an alternative username'
],
# IP ranges
[
'1.2.3.4/31',
0, '1.2.3.4/31', false,
'Anonymous with IPv4 range'
],
[
'2001:db8::1/43',
0, '2001:db8::1/43', false,
'Anonymous with IPv6 range'
],
# External (imported) user, unknown prefix
[
'acme>Alice',
0, "acme>Alice", false,
'User from acme wiki'
],
# Corrupt user names
[
"Foo\nBar",
0, "Foo\nBar", false,
'User name with line break'
],
[
'Barf_',
0, "Barf_", false,
'User name with trailing underscore'
],
[
'abcd',
0, "abcd", false,
'Lower case user name'
],
[
'For/Bar',
0, "For/Bar", false,
'User name with slash'
],
[
'For#Bar',
0, "For#Bar", false,
'User name with hash'
],
# ## Regular user ##########################################
# TODO!
];
}
/**
* @dataProvider provideUserToolLinks
* @covers Linker::userToolLinks
* @param string $expected
* @param int $userId
* @param string $userText
*/
public function testUserToolLinks( $expected, $userId, $userText ) {
// We'd also test the warning, but injecting a mock logger into a static method is tricky.
if ( $userText === '' ) {
$actual = @Linker::userToolLinks( $userId, $userText );
} else {
$actual = Linker::userToolLinks( $userId, $userText );
}
$this->assertSame( $expected, $actual );
}
public static function provideUserToolLinks() {
return [
// Empty name (T222529)
'Empty username, userid 0' => [ ' (no username available)', 0, '' ],
'Empty username, userid > 0' => [ ' (no username available)', 73, '' ],
];
}
/**
* @dataProvider provideUserTalkLink
* @covers Linker::userTalkLink
* @param string $expected
* @param int $userId
* @param string $userText
*/
public function testUserTalkLink( $expected, $userId, $userText ) {
// We'd also test the warning, but injecting a mock logger into a static method is tricky.
if ( $userText === '' ) {
$actual = @Linker::userTalkLink( $userId, $userText );
} else {
$actual = Linker::userTalkLink( $userId, $userText );
}
$this->assertSame( $expected, $actual );
}
public static function provideUserTalkLink() {
return [
// Empty name (T222529)
'Empty username, userid 0' => [ '(no username available)', 0, '' ],
'Empty username, userid > 0' => [ '(no username available)', 73, '' ],
];
}
/**
* @dataProvider provideBlockLink
* @covers Linker::blockLink
* @param string $expected
* @param int $userId
* @param string $userText
*/
public function testBlockLink( $expected, $userId, $userText ) {
// We'd also test the warning, but injecting a mock logger into a static method is tricky.
if ( $userText === '' ) {
$actual = @Linker::blockLink( $userId, $userText );
} else {
$actual = Linker::blockLink( $userId, $userText );
}
$this->assertSame( $expected, $actual );
}
public static function provideBlockLink() {
return [
// Empty name (T222529)
'Empty username, userid 0' => [ '(no username available)', 0, '' ],
'Empty username, userid > 0' => [ '(no username available)', 73, '' ],
];
}
/**
* @dataProvider provideEmailLink
* @covers Linker::emailLink
* @param string $expected
* @param int $userId
* @param string $userText
*/
public function testEmailLink( $expected, $userId, $userText ) {
// We'd also test the warning, but injecting a mock logger into a static method is tricky.
if ( $userText === '' ) {
$actual = @Linker::emailLink( $userId, $userText );
} else {
$actual = Linker::emailLink( $userId, $userText );
}
$this->assertSame( $expected, $actual );
}
public static function provideEmailLink() {
return [
// Empty name (T222529)
'Empty username, userid 0' => [ '(no username available)', 0, '' ],
'Empty username, userid > 0' => [ '(no username available)', 73, '' ],
];
}
/**
* @dataProvider provideCasesForFormatComment
* @covers Linker::formatComment
* @covers Linker::formatLinksInComment
* @covers \MediaWiki\CommentFormatter\CommentParser
* @covers \MediaWiki\CommentFormatter\CommentFormatter
*/
public function testFormatComment(
$expected, $comment, $title = false, $local = false, $wikiId = null
) {
$conf = new SiteConfiguration();
$conf->settings = [
'wgServer' => [
'enwiki' => '//en.example.org',
'dewiki' => '//de.example.org',
],
'wgArticlePath' => [
'enwiki' => '/w/$1',
'dewiki' => '/w/$1',
],
];
$conf->suffixes = [ 'wiki' ];
$this->setMwGlobals( 'wgConf', $conf );
$this->overrideConfigValues( [
MainConfigNames::Script => '/wiki/index.php',
MainConfigNames::ArticlePath => '/wiki/$1',
MainConfigNames::CapitalLinks => true,
// TODO: update tests when the default changes
MainConfigNames::FragmentMode => [ 'legacy' ],
] );
if ( $title === false ) {
// We need a page title that exists
$title = Title::newFromText( 'Special:BlankPage' );
}
$this->assertEquals(
$expected,
Linker::formatComment( $comment, $title, $local, $wikiId )
);
}
public function provideCasesForFormatComment() {
$wikiId = 'enwiki'; // $wgConf has a fake entry for this
return [
// Linker::formatComment
[
'a<script>b',
'a */"
],
[
'',
"/* autocomment */",
false, true
],
[
'',
"/* autocomment */",
null
],
[
'',
"/* */",
false, true
],
[
'',
"/* */",
null
],
[
'',
"/* [[ */",
false, true
],
[
'',
"/* [[ */",
null
],
[
"foo ",
"foo /* [[#_\t_]] */",
false, true
],
[
"foo ",
"foo /* [[#_\t_]] */",
null
],
[
'',
"/* autocomment */",
false, false
],
[
'',
"/* autocomment */",
false, false, $wikiId
],
// Linker::formatLinksInComment
[
'abc link def',
"abc [[link]] def",
],
[
'abc text def',
"abc [[link|text]] def",
],
[
'abc Special:BlankPage def',
"abc [[Special:BlankPage|]] def",
],
[
'abc ąśż def',
"abc [[%C4%85%C5%9B%C5%BC]] def",
],
[
'abc #section def',
"abc [[#section]] def",
],
[
'abc /subpage def',
"abc [[/subpage]] def",
],
[
'abc "evil!" def',
"abc [[\"evil!\"]] def",
],
[
'abc [[<script>very evil</script>]] def',
"abc [[]] def",
],
[
'abc [[|]] def',
"abc [[|]] def",
],
[
'abc link def',
"abc [[link]] def",
false, false
],
[
'abc link def',
"abc [[link]] def",
false, false, $wikiId
],
[
'Media:LinkerTest.jpg',
'[[Media:LinkerTest.jpg]]'
],
[
'Special:BlankPage',
'[[:Special:BlankPage]]'
],
[
'linktrail...',
'[[link]]trail...'
]
];
// phpcs:enable
}
/**
* @covers Linker::formatLinksInComment
* @covers \MediaWiki\CommentFormatter\CommentParser
* @covers \MediaWiki\CommentFormatter\CommentFormatter
* @dataProvider provideCasesForFormatLinksInComment
*/
public function testFormatLinksInComment( $expected, $input, $wiki ) {
$conf = new SiteConfiguration();
$conf->settings = [
'wgServer' => [
'enwiki' => '//en.example.org'
],
'wgArticlePath' => [
'enwiki' => '/w/$1',
],
];
$conf->suffixes = [ 'wiki' ];
$this->setMwGlobals( 'wgConf', $conf );
$this->overrideConfigValues( [
MainConfigNames::Script => '/wiki/index.php',
MainConfigNames::ArticlePath => '/wiki/$1',
MainConfigNames::CapitalLinks => true,
] );
$this->assertEquals(
$expected,
Linker::formatLinksInComment( $input, Title::newFromText( 'Special:BlankPage' ), false, $wiki )
);
}
/**
* @covers Linker::generateRollback
* @dataProvider provideCasesForRollbackGeneration
*/
public function testGenerateRollback( $rollbackEnabled, $expectedModules, $title ) {
$context = RequestContext::getMain();
$user = $context->getUser();
$this->getServiceContainer()->getUserOptionsManager()->setOption(
$user,
'showrollbackconfirmation',
$rollbackEnabled
);
$this->assertSame( 0, Title::newFromText( $title )->getArticleID() );
$pageData = $this->insertPage( $title );
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $pageData['title'] );
$summary = CommentStoreComment::newUnsavedComment( 'Some comment!' );
$page->newPageUpdater( $user )
->setContent(
SlotRecord::MAIN,
new TextContent( 'Technical Wishes 123!' )
)
->saveRevision( $summary );
$rollbackOutput = Linker::generateRollback( $page->getRevisionRecord(), $context );
$modules = $context->getOutput()->getModules();
$currentRev = $page->getRevisionRecord();
$revisionLookup = $this->getServiceContainer()->getRevisionLookup();
$oldestRev = $revisionLookup->getFirstRevision( $page->getTitle() );
$this->assertEquals( $expectedModules, $modules );
$this->assertInstanceOf( RevisionRecord::class, $currentRev );
$this->assertInstanceOf( User::class, $currentRev->getUser() );
$this->assertEquals( $user->getName(), $currentRev->getUser()->getName() );
$this->assertEquals(
static::getTestSysop()->getUser(),
$oldestRev->getUser()->getName()
);
$ids = [];
$r = $oldestRev;
while ( $r ) {
$ids[] = $r->getId();
$r = $revisionLookup->getNextRevision( $r );
}
$this->assertEquals( [ $oldestRev->getId(), $currentRev->getId() ], $ids );
$this->assertStringContainsString( 'rollback 1 edit', $rollbackOutput );
}
public static function provideCasesForRollbackGeneration() {
return [
[
true,
[ 'mediawiki.misc-authed-curate' ],
'Rollback_Test_Page'
],
[
false,
[],
'Rollback_Test_Page2'
]
];
}
public static function provideCasesForFormatLinksInComment() {
return [
[
'foo bar Special:BlankPage',
'foo bar [[Special:BlankPage]]',
null,
],
[
'Special:BlankPage',
'[[ :Special:BlankPage]]',
null,
],
[
':Special:BlankPage',
'[[::Special:BlankPage]]',
null,
],
[
'[[FooSpecial:BlankPage',
'[[Foo[[Special:BlankPage]]',
null,
],
[
'Foo\'bar',
"[[Foo'bar]]",
'enwiki',
],
[
'foo bar Special:BlankPage',
'foo bar [[Special:BlankPage]]',
'enwiki',
],
[
'foo bar Image:Example',
'foo bar [[Image:Example]]',
'enwiki',
],
];
// phpcs:enable
}
public static function provideTooltipAndAccesskeyAttribs() {
return [
'Watch no expiry' => [
'ca-watch', [], null, [ 'title' => 'Add this page to your watchlist [w]', 'accesskey' => 'w' ]
],
'Key does not exist' => [
'key-does-not-exist', [], null, []
],
'Unwatch no expiry' => [
'ca-unwatch', [], null, [ 'title' => 'Remove this page from your watchlist [w]',
'accesskey' => 'w' ]
],
];
}
/**
* @covers Linker::tooltipAndAccesskeyAttribs
* @dataProvider provideTooltipAndAccesskeyAttribs
*/
public function testTooltipAndAccesskeyAttribs( $name, $msgParams, $options, $expected ) {
$this->overrideConfigValue( MainConfigNames::WatchlistExpiry, true );
$user = $this->createMock( User::class );
$user->method( 'isRegistered' )->willReturn( true );
$title = SpecialPage::getTitleFor( 'Blankpage' );
$context = RequestContext::getMain();
$context->setTitle( $title );
$context->setUser( $user );
$watchedItemWithoutExpiry = new WatchedItem( $user, $title, null, null );
$result = Linker::tooltipAndAccesskeyAttribs( $name, $msgParams, $options );
$this->assertEquals( $expected, $result );
}
/**
* @covers Linker::commentBlock
* @dataProvider provideCommentBlock
*/
public function testCommentBlock(
$expected, $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
) {
$conf = new SiteConfiguration();
$conf->settings = [
'wgServer' => [
'enwiki' => '//en.example.org'
],
'wgArticlePath' => [
'enwiki' => '/w/$1',
],
];
$conf->suffixes = [ 'wiki' ];
$this->setMwGlobals( 'wgConf', $conf );
$this->overrideConfigValues( [
MainConfigNames::Script => '/wiki/index.php',
MainConfigNames::ArticlePath => '/wiki/$1',
MainConfigNames::CapitalLinks => true,
] );
$this->assertEquals( $expected, Linker::commentBlock( $comment, $title, $local, $wikiId, $useParentheses ) );
}
public static function provideCommentBlock() {
return [
[
' ',
'Test'
],
'Empty comment' => [ '', '' ],
'Backwards compatibility empty comment' => [ '', '*' ],
'No parenthesis' => [
' ',
'Test',
null, false, null,
false
],
'Page exist link' => [
' ',
'[[Special:BlankPage]]'
],
'Page does not exist link' => [
' ',
'[[Test]]'
],
'Link to other page section' => [
' ',
'[[#Test]]',
Title::newFromText( 'Special:BlankPage' )
],
'$local is true' => [
' ',
'[[#Test]]',
Title::newFromText( 'Special:BlankPage' ),
true
],
'Given wikiId' => [
' ',
'[[Test]]',
null, false,
'enwiki'
],
'Section link to external wiki page' => [
' ',
'[[#Test]]',
Title::newFromText( 'Special:BlankPage' ),
false,
'enwiki'
],
];
}
/**
* @covers Linker::revComment
* @dataProvider provideRevComment
*/
public function testRevComment(
string $expected,
bool $isSysop = false,
int $visibility = 0,
bool $local = false,
bool $isPublic = false,
bool $useParentheses = true,
?string $comment = 'Some comment!'
) {
$pageData = $this->insertPage( 'RevCommentTestPage' );
$revisionRecord = new MutableRevisionRecord( $pageData['title'] );
if ( $comment ) {
$revisionRecord->setComment( CommentStoreComment::newUnsavedComment( $comment ) );
}
$revisionRecord->setVisibility( $visibility );
$context = RequestContext::getMain();
$user = $isSysop ? $this->getTestSysop()->getUser() : $this->getTestUser()->getUser();
$context->setUser( $user );
$this->assertEquals( $expected, Linker::revComment( $revisionRecord, $local, $isPublic, $useParentheses ) );
}
public static function provideRevComment() {
return [
'Should be visible' => [
' '
],
'Should not have parenthesis' => [
' ',
false, 0, false, false,
false
],
'Should be empty' => [
'',
false, 0, false, false, true,
null
],
'Deleted comment should not be visible to normal users' => [
' ',
false,
RevisionRecord::DELETED_COMMENT
],
'Deleted comment should not be visible to normal users even if public' => [
' ',
false,
RevisionRecord::DELETED_COMMENT,
false,
true
],
'Deleted comment should be visible to sysops' => [
' ',
true,
RevisionRecord::DELETED_COMMENT
],
];
}
/**
* @covers Linker::specialLink
* @dataProvider provideSpecialLink
*/
public function testSpecialLink( $expected, $target, $key = null ) {
$this->overrideConfigValues( [
MainConfigNames::Script => '/w/index.php',
MainConfigNames::ArticlePath => '/wiki/$1',
] );
$this->assertEquals( $expected, Linker::specialLink( $target, $key ) );
}
public static function provideSpecialLink() {
yield 'Recent Changes' => [
'Recent changes',
'Recentchanges'
];
yield 'Recent Changes, only for a given tag' => [
'Recent changes',
'Recentchanges?tagfilter=blanking'
];
yield 'Contributions' => [
'User contributions',
'Contributions'
];
yield 'Contributions, custom key' => [
'⧼made-up-display-key⧽',
'Contributions',
'made-up-display-key'
];
yield 'Contributions, targetted' => [
'User contributions',
'Contributions/JohnDoe'
];
yield 'Contributions, targetted, topOnly' => [
'User contributions',
'Contributions/JohnDoe?topOnly=1'
];
yield 'Userlogin' => [
'Log in',
'Userlogin',
'login'
];
yield 'Userlogin, returnto' => [
'Log in',
'Userlogin?returnto=Main+Page',
'login'
];
yield 'Userlogin, targetted' => [
// Note that this special page doesn't have any support for and doesn't do anything with
// the subtitle; this is here as demonstration that Linker doesn't care.
'Log in',
'Userlogin/JohnDoe',
'login'
];
}
}