1<?php 2 3use MediaWiki\MediaWikiServices; 4use MediaWiki\Revision\RevisionRecord; 5 6/** 7 * @group Database 8 */ 9class LinkerTest extends MediaWikiLangTestCase { 10 /** 11 * @dataProvider provideCasesForUserLink 12 * @covers Linker::userLink 13 */ 14 public function testUserLink( $expected, $userId, $userName, $altUserName = false, $msg = '' ) { 15 $this->setMwGlobals( [ 16 'wgArticlePath' => '/wiki/$1', 17 ] ); 18 19 // We'd also test the warning, but injecting a mock logger into a static method is tricky. 20 if ( !$userName ) { 21 Wikimedia\suppressWarnings(); 22 } 23 $actual = Linker::userLink( $userId, $userName, $altUserName ); 24 if ( !$userName ) { 25 Wikimedia\restoreWarnings(); 26 } 27 28 $this->assertEquals( $expected, $actual, $msg ); 29 } 30 31 public static function provideCasesForUserLink() { 32 # Format: 33 # - expected 34 # - userid 35 # - username 36 # - optional altUserName 37 # - optional message 38 return [ 39 # Empty name (T222529) 40 'Empty username, userid 0' => [ '(no username available)', 0, '' ], 41 'Empty username, userid > 0' => [ '(no username available)', 73, '' ], 42 43 'false instead of username' => [ '(no username available)', 73, false ], 44 'null instead of username' => [ '(no username available)', 0, null ], 45 46 # ## ANONYMOUS USER ######################################## 47 [ 48 '<a href="/wiki/Special:Contributions/JohnDoe" ' 49 . 'class="mw-userlink mw-anonuserlink" ' 50 . 'title="Special:Contributions/JohnDoe"><bdi>JohnDoe</bdi></a>', 51 0, 'JohnDoe', false, 52 ], 53 [ 54 '<a href="/wiki/Special:Contributions/::1" ' 55 . 'class="mw-userlink mw-anonuserlink" ' 56 . 'title="Special:Contributions/::1"><bdi>::1</bdi></a>', 57 0, '::1', false, 58 'Anonymous with pretty IPv6' 59 ], 60 [ 61 '<a href="/wiki/Special:Contributions/0:0:0:0:0:0:0:1" ' 62 . 'class="mw-userlink mw-anonuserlink" ' 63 . 'title="Special:Contributions/0:0:0:0:0:0:0:1"><bdi>::1</bdi></a>', 64 0, '0:0:0:0:0:0:0:1', false, 65 'Anonymous with almost pretty IPv6' 66 ], 67 [ 68 '<a href="/wiki/Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" ' 69 . 'class="mw-userlink mw-anonuserlink" ' 70 . 'title="Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001"><bdi>::1</bdi></a>', 71 0, '0000:0000:0000:0000:0000:0000:0000:0001', false, 72 'Anonymous with full IPv6' 73 ], 74 [ 75 '<a href="/wiki/Special:Contributions/::1" ' 76 . 'class="mw-userlink mw-anonuserlink" ' 77 . 'title="Special:Contributions/::1"><bdi>AlternativeUsername</bdi></a>', 78 0, '::1', 'AlternativeUsername', 79 'Anonymous with pretty IPv6 and an alternative username' 80 ], 81 82 # IPV4 83 [ 84 '<a href="/wiki/Special:Contributions/127.0.0.1" ' 85 . 'class="mw-userlink mw-anonuserlink" ' 86 . 'title="Special:Contributions/127.0.0.1"><bdi>127.0.0.1</bdi></a>', 87 0, '127.0.0.1', false, 88 'Anonymous with IPv4' 89 ], 90 [ 91 '<a href="/wiki/Special:Contributions/127.0.0.1" ' 92 . 'class="mw-userlink mw-anonuserlink" ' 93 . 'title="Special:Contributions/127.0.0.1"><bdi>AlternativeUsername</bdi></a>', 94 0, '127.0.0.1', 'AlternativeUsername', 95 'Anonymous with IPv4 and an alternative username' 96 ], 97 98 # ## Regular user ########################################## 99 # TODO! 100 ]; 101 } 102 103 /** 104 * @dataProvider provideUserToolLinks 105 * @covers Linker::userToolLinks 106 * @param string $expected 107 * @param int $userId 108 * @param string $userText 109 */ 110 public function testUserToolLinks( $expected, $userId, $userText ) { 111 // We'd also test the warning, but injecting a mock logger into a static method is tricky. 112 if ( $userText === '' ) { 113 Wikimedia\suppressWarnings(); 114 } 115 $actual = Linker::userToolLinks( $userId, $userText ); 116 if ( $userText === '' ) { 117 Wikimedia\restoreWarnings(); 118 } 119 120 $this->assertSame( $expected, $actual ); 121 } 122 123 public static function provideUserToolLinks() { 124 return [ 125 // Empty name (T222529) 126 'Empty username, userid 0' => [ ' (no username available)', 0, '' ], 127 'Empty username, userid > 0' => [ ' (no username available)', 73, '' ], 128 ]; 129 } 130 131 /** 132 * @dataProvider provideUserTalkLink 133 * @covers Linker::userTalkLink 134 * @param string $expected 135 * @param int $userId 136 * @param string $userText 137 */ 138 public function testUserTalkLink( $expected, $userId, $userText ) { 139 // We'd also test the warning, but injecting a mock logger into a static method is tricky. 140 if ( $userText === '' ) { 141 Wikimedia\suppressWarnings(); 142 } 143 $actual = Linker::userTalkLink( $userId, $userText ); 144 if ( $userText === '' ) { 145 Wikimedia\restoreWarnings(); 146 } 147 148 $this->assertSame( $expected, $actual ); 149 } 150 151 public static function provideUserTalkLink() { 152 return [ 153 // Empty name (T222529) 154 'Empty username, userid 0' => [ '(no username available)', 0, '' ], 155 'Empty username, userid > 0' => [ '(no username available)', 73, '' ], 156 ]; 157 } 158 159 /** 160 * @dataProvider provideBlockLink 161 * @covers Linker::blockLink 162 * @param string $expected 163 * @param int $userId 164 * @param string $userText 165 */ 166 public function testBlockLink( $expected, $userId, $userText ) { 167 // We'd also test the warning, but injecting a mock logger into a static method is tricky. 168 if ( $userText === '' ) { 169 Wikimedia\suppressWarnings(); 170 } 171 $actual = Linker::blockLink( $userId, $userText ); 172 if ( $userText === '' ) { 173 Wikimedia\restoreWarnings(); 174 } 175 176 $this->assertSame( $expected, $actual ); 177 } 178 179 public static function provideBlockLink() { 180 return [ 181 // Empty name (T222529) 182 'Empty username, userid 0' => [ '(no username available)', 0, '' ], 183 'Empty username, userid > 0' => [ '(no username available)', 73, '' ], 184 ]; 185 } 186 187 /** 188 * @dataProvider provideEmailLink 189 * @covers Linker::emailLink 190 * @param string $expected 191 * @param int $userId 192 * @param string $userText 193 */ 194 public function testEmailLink( $expected, $userId, $userText ) { 195 // We'd also test the warning, but injecting a mock logger into a static method is tricky. 196 if ( $userText === '' ) { 197 Wikimedia\suppressWarnings(); 198 } 199 $actual = Linker::emailLink( $userId, $userText ); 200 if ( $userText === '' ) { 201 Wikimedia\restoreWarnings(); 202 } 203 204 $this->assertSame( $expected, $actual ); 205 } 206 207 public static function provideEmailLink() { 208 return [ 209 // Empty name (T222529) 210 'Empty username, userid 0' => [ '(no username available)', 0, '' ], 211 'Empty username, userid > 0' => [ '(no username available)', 73, '' ], 212 ]; 213 } 214 215 /** 216 * @dataProvider provideCasesForFormatComment 217 * @covers Linker::formatComment 218 * @covers Linker::formatAutocomments 219 * @covers Linker::formatLinksInComment 220 */ 221 public function testFormatComment( 222 $expected, $comment, $title = false, $local = false, $wikiId = null 223 ) { 224 $conf = new SiteConfiguration(); 225 $conf->settings = [ 226 'wgServer' => [ 227 'enwiki' => '//en.example.org', 228 'dewiki' => '//de.example.org', 229 ], 230 'wgArticlePath' => [ 231 'enwiki' => '/w/$1', 232 'dewiki' => '/w/$1', 233 ], 234 ]; 235 $conf->suffixes = [ 'wiki' ]; 236 237 $this->setMwGlobals( [ 238 'wgScript' => '/wiki/index.php', 239 'wgArticlePath' => '/wiki/$1', 240 'wgCapitalLinks' => true, 241 'wgConf' => $conf, 242 // TODO: update tests when the default changes 243 'wgFragmentMode' => [ 'legacy' ], 244 ] ); 245 246 if ( $title === false ) { 247 // We need a page title that exists 248 $title = Title::newFromText( 'Special:BlankPage' ); 249 } 250 251 $this->assertEquals( 252 $expected, 253 Linker::formatComment( $comment, $title, $local, $wikiId ) 254 ); 255 } 256 257 public function provideCasesForFormatComment() { 258 $wikiId = 'enwiki'; // $wgConf has a fake entry for this 259 260 // phpcs:disable Generic.Files.LineLength 261 return [ 262 // Linker::formatComment 263 [ 264 'a<script>b', 265 'a<script>b', 266 ], 267 [ 268 'a—b', 269 'a—b', 270 ], 271 [ 272 "'''not bolded'''", 273 "'''not bolded'''", 274 ], 275 [ 276 "try <script>evil</scipt> things", 277 "try <script>evil</scipt> things", 278 ], 279 // Linker::formatAutocomments 280 [ 281 '<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a></span></span>', 282 "/* autocomment */", 283 ], 284 [ 285 '<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#linkie.3F" title="Special:BlankPage">→[[linkie?]]</a></span></span>', 286 "/* [[linkie?]] */", 287 ], 288 [ 289 '<span dir="auto"><span class="autocomment">: </span> // Edit via via</span>', 290 // Regression test for T222857 291 "/* */ // Edit via via", 292 ], 293 [ 294 '<span dir="auto"><span class="autocomment">: </span> foobar</span>', 295 // Regression test for T222857 296 "/**/ foobar", 297 ], 298 [ 299 '<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a>: </span> post</span>', 300 "/* autocomment */ post", 301 ], 302 [ 303 'pre <span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a></span></span>', 304 "pre /* autocomment */", 305 ], 306 [ 307 'pre <span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a>: </span> post</span>', 308 "pre /* autocomment */ post", 309 ], 310 [ 311 '<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a>: </span> multiple? <span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment2" title="Special:BlankPage">→autocomment2</a></span></span></span>', 312 "/* autocomment */ multiple? /* autocomment2 */", 313 ], 314 [ 315 '<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment_containing_.2F.2A" title="Special:BlankPage">→autocomment containing /*</a>: </span> T70361</span>', 316 "/* autocomment containing /* */ T70361" 317 ], 318 [ 319 '<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment_containing_.22quotes.22" title="Special:BlankPage">→autocomment containing "quotes"</a></span></span>', 320 "/* autocomment containing \"quotes\" */" 321 ], 322 [ 323 '<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment_containing_.3Cscript.3Etags.3C.2Fscript.3E" title="Special:BlankPage">→autocomment containing <script>tags</script></a></span></span>', 324 "/* autocomment containing <script>tags</script> */" 325 ], 326 [ 327 '<span dir="auto"><span class="autocomment"><a href="#autocomment">→autocomment</a></span></span>', 328 "/* autocomment */", 329 false, true 330 ], 331 [ 332 '<span dir="auto"><span class="autocomment">autocomment</span></span>', 333 "/* autocomment */", 334 null 335 ], 336 [ 337 '', 338 "/* */", 339 false, true 340 ], 341 [ 342 '', 343 "/* */", 344 null 345 ], 346 [ 347 '<span dir="auto"><span class="autocomment">[[</span></span>', 348 "/* [[ */", 349 false, true 350 ], 351 [ 352 '<span dir="auto"><span class="autocomment">[[</span></span>', 353 "/* [[ */", 354 null 355 ], 356 [ 357 "foo <span dir=\"auto\"><span class=\"autocomment\"><a href=\"#.23\">→[[#_\t_]]</a></span></span>", 358 "foo /* [[#_\t_]] */", 359 false, true 360 ], 361 [ 362 "foo <span dir=\"auto\"><span class=\"autocomment\"><a href=\"#_.09\">#_\t_</a></span></span>", 363 "foo /* [[#_\t_]] */", 364 null 365 ], 366 [ 367 '<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a></span></span>', 368 "/* autocomment */", 369 false, false 370 ], 371 [ 372 '<span dir="auto"><span class="autocomment"><a class="external" rel="nofollow" href="//en.example.org/w/Special:BlankPage#autocomment">→autocomment</a></span></span>', 373 "/* autocomment */", 374 false, false, $wikiId 375 ], 376 // Linker::formatLinksInComment 377 [ 378 'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">link</a> def', 379 "abc [[link]] def", 380 ], 381 [ 382 'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">text</a> def', 383 "abc [[link|text]] def", 384 ], 385 [ 386 'abc <a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a> def', 387 "abc [[Special:BlankPage|]] def", 388 ], 389 [ 390 'abc <a href="/wiki/index.php?title=%C4%84%C5%9B%C5%BC&action=edit&redlink=1" class="new" title="Ąśż (page does not exist)">ąśż</a> def', 391 "abc [[%C4%85%C5%9B%C5%BC]] def", 392 ], 393 [ 394 'abc <a href="/wiki/Special:BlankPage#section" title="Special:BlankPage">#section</a> def', 395 "abc [[#section]] def", 396 ], 397 [ 398 'abc <a href="/wiki/index.php?title=/subpage&action=edit&redlink=1" class="new" title="/subpage (page does not exist)">/subpage</a> def', 399 "abc [[/subpage]] def", 400 ], 401 [ 402 'abc <a href="/wiki/index.php?title=%22evil!%22&action=edit&redlink=1" class="new" title=""evil!" (page does not exist)">"evil!"</a> def', 403 "abc [[\"evil!\"]] def", 404 ], 405 [ 406 'abc [[<script>very evil</script>]] def', 407 "abc [[<script>very evil</script>]] def", 408 ], 409 [ 410 'abc [[|]] def', 411 "abc [[|]] def", 412 ], 413 [ 414 'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">link</a> def', 415 "abc [[link]] def", 416 false, false 417 ], 418 [ 419 'abc <a class="external" rel="nofollow" href="//en.example.org/w/Link">link</a> def', 420 "abc [[link]] def", 421 false, false, $wikiId 422 ], 423 ]; 424 // phpcs:enable 425 } 426 427 /** 428 * @covers Linker::formatLinksInComment 429 * @dataProvider provideCasesForFormatLinksInComment 430 */ 431 public function testFormatLinksInComment( $expected, $input, $wiki ) { 432 $conf = new SiteConfiguration(); 433 $conf->settings = [ 434 'wgServer' => [ 435 'enwiki' => '//en.example.org' 436 ], 437 'wgArticlePath' => [ 438 'enwiki' => '/w/$1', 439 ], 440 ]; 441 $conf->suffixes = [ 'wiki' ]; 442 $this->setMwGlobals( [ 443 'wgScript' => '/wiki/index.php', 444 'wgArticlePath' => '/wiki/$1', 445 'wgCapitalLinks' => true, 446 'wgConf' => $conf, 447 ] ); 448 449 $this->assertEquals( 450 $expected, 451 Linker::formatLinksInComment( $input, Title::newFromText( 'Special:BlankPage' ), false, $wiki ) 452 ); 453 } 454 455 /** 456 * @covers Linker::generateRollback 457 * @dataProvider provideCasesForRollbackGeneration 458 */ 459 public function testGenerateRollback( $rollbackEnabled, $expectedModules, $title ) { 460 $this->markTestSkippedIfDbType( 'postgres' ); 461 462 $context = RequestContext::getMain(); 463 $user = $context->getUser(); 464 $user->setOption( 'showrollbackconfirmation', $rollbackEnabled ); 465 466 $this->assertSame( 0, Title::newFromText( $title )->getArticleID() ); 467 $pageData = $this->insertPage( $title ); 468 $page = WikiPage::factory( $pageData['title'] ); 469 470 $updater = $page->newPageUpdater( $user ); 471 $updater->setContent( \MediaWiki\Revision\SlotRecord::MAIN, 472 new TextContent( 'Technical Wishes 123!' ) 473 ); 474 $summary = CommentStoreComment::newUnsavedComment( 'Some comment!' ); 475 $updater->saveRevision( $summary ); 476 477 $rollbackOutput = Linker::generateRollback( $page->getRevisionRecord(), $context ); 478 $modules = $context->getOutput()->getModules(); 479 $currentRev = $page->getRevisionRecord(); 480 $revisionLookup = MediaWikiServices::getInstance()->getRevisionLookup(); 481 $oldestRev = $revisionLookup->getFirstRevision( $page->getTitle() ); 482 483 $this->assertEquals( $expectedModules, $modules ); 484 $this->assertInstanceOf( RevisionRecord::class, $currentRev ); 485 $this->assertInstanceOf( User::class, $currentRev->getUser() ); 486 $this->assertEquals( $user->getName(), $currentRev->getUser()->getName() ); 487 $this->assertEquals( 488 static::getTestSysop()->getUser(), 489 $oldestRev->getUser()->getName() 490 ); 491 492 $ids = []; 493 $r = $oldestRev; 494 while ( $r ) { 495 $ids[] = $r->getId(); 496 $r = $revisionLookup->getNextRevision( $r ); 497 } 498 $this->assertEquals( [ $oldestRev->getId(), $currentRev->getId() ], $ids ); 499 500 $this->assertStringContainsString( 'rollback 1 edit', $rollbackOutput ); 501 } 502 503 public static function provideCasesForRollbackGeneration() { 504 return [ 505 [ 506 true, 507 [ 'mediawiki.misc-authed-curate' ], 508 'Rollback_Test_Page' 509 ], 510 [ 511 false, 512 [], 513 'Rollback_Test_Page2' 514 ] 515 ]; 516 } 517 518 public static function provideCasesForFormatLinksInComment() { 519 // phpcs:disable Generic.Files.LineLength 520 return [ 521 [ 522 'foo bar <a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>', 523 'foo bar [[Special:BlankPage]]', 524 null, 525 ], 526 [ 527 '<a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>', 528 '[[ :Special:BlankPage]]', 529 null, 530 ], 531 [ 532 '[[Foo<a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>', 533 '[[Foo[[Special:BlankPage]]', 534 null, 535 ], 536 [ 537 '<a class="external" rel="nofollow" href="//en.example.org/w/Foo%27bar">Foo\'bar</a>', 538 "[[Foo'bar]]", 539 'enwiki', 540 ], 541 [ 542 'foo bar <a class="external" rel="nofollow" href="//en.example.org/w/Special:BlankPage">Special:BlankPage</a>', 543 'foo bar [[Special:BlankPage]]', 544 'enwiki', 545 ], 546 [ 547 'foo bar <a class="external" rel="nofollow" href="//en.example.org/w/File:Example">Image:Example</a>', 548 'foo bar [[Image:Example]]', 549 'enwiki', 550 ], 551 ]; 552 // phpcs:enable 553 } 554 555 public static function provideLinkBeginHook() { 556 // phpcs:disable Generic.Files.LineLength 557 return [ 558 // Modify $html 559 [ 560 function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) { 561 $html = 'foobar'; 562 }, 563 '<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>' 564 ], 565 // Modify $attribs 566 [ 567 function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) { 568 $attribs['bar'] = 'baz'; 569 }, 570 '<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>' 571 ], 572 // Modify $query 573 [ 574 function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) { 575 $query['bar'] = 'baz'; 576 }, 577 '<a href="/w/index.php?title=Special:BlankPage&bar=baz" title="Special:BlankPage">Special:BlankPage</a>' 578 ], 579 // Force HTTP $options 580 [ 581 function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) { 582 $options = [ 'http' ]; 583 }, 584 '<a href="http://example.org/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>' 585 ], 586 // Force 'forcearticlepath' in $options 587 [ 588 function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) { 589 $options = [ 'forcearticlepath' ]; 590 $query['foo'] = 'bar'; 591 }, 592 '<a href="/wiki/Special:BlankPage?foo=bar" title="Special:BlankPage">Special:BlankPage</a>' 593 ], 594 // Abort early 595 [ 596 function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) { 597 $ret = 'foobar'; 598 return false; 599 }, 600 'foobar' 601 ], 602 ]; 603 // phpcs:enable 604 } 605 606 /** 607 * @covers MediaWiki\Linker\LinkRenderer::runLegacyBeginHook 608 * @dataProvider provideLinkBeginHook 609 */ 610 public function testLinkBeginHook( $callback, $expected ) { 611 $this->hideDeprecated( 'LinkBegin hook (used in hook-LinkBegin-closure)' ); 612 $this->setMwGlobals( [ 613 'wgArticlePath' => '/wiki/$1', 614 'wgServer' => '//example.org', 615 'wgCanonicalServer' => 'http://example.org', 616 'wgScriptPath' => '/w', 617 'wgScript' => '/w/index.php', 618 ] ); 619 620 $this->setMwGlobals( 'wgHooks', [ 'LinkBegin' => [ $callback ] ] ); 621 $title = SpecialPage::getTitleFor( 'Blankpage' ); 622 $out = Linker::link( $title ); 623 $this->assertEquals( $expected, $out ); 624 } 625 626 public static function provideLinkEndHook() { 627 return [ 628 // Override $html 629 [ 630 function ( $dummy, $title, $options, &$html, &$attribs, &$ret ) { 631 $html = 'foobar'; 632 }, 633 '<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>' 634 ], 635 // Modify $attribs 636 [ 637 function ( $dummy, $title, $options, &$html, &$attribs, &$ret ) { 638 $attribs['bar'] = 'baz'; 639 }, 640 '<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>' 641 ], 642 // Fully override return value and abort hook 643 [ 644 function ( $dummy, $title, $options, &$html, &$attribs, &$ret ) { 645 $ret = 'blahblahblah'; 646 return false; 647 }, 648 'blahblahblah' 649 ], 650 651 ]; 652 } 653 654 /** 655 * @covers MediaWiki\Linker\LinkRenderer::buildAElement 656 * @dataProvider provideLinkEndHook 657 */ 658 public function testLinkEndHook( $callback, $expected ) { 659 $this->hideDeprecated( 'LinkEnd hook (used in hook-LinkEnd-closure)' ); 660 $this->setMwGlobals( [ 661 'wgArticlePath' => '/wiki/$1', 662 ] ); 663 664 $this->setMwGlobals( 'wgHooks', [ 'LinkEnd' => [ $callback ] ] ); 665 666 $title = SpecialPage::getTitleFor( 'Blankpage' ); 667 $out = Linker::link( $title ); 668 $this->assertEquals( $expected, $out ); 669 } 670 671 public static function provideTooltipAndAccesskeyAttribs() { 672 return [ 673 'Watch no expiry' => [ 674 'ca-watch', [], null, [ 'title' => 'Add this page to your watchlist [w]', 'accesskey' => 'w' ] 675 ], 676 'Key does not exist' => [ 677 'key-does-not-exist', [], null, [] 678 ], 679 'Unwatch no expiry' => [ 680 'ca-unwatch', [], null, [ 'title' => 'Remove this page from your watchlist [w]', 681 'accesskey' => 'w' ] 682 ], 683 ]; 684 } 685 686 /** 687 * @covers Linker::tooltipAndAccesskeyAttribs 688 * @dataProvider provideTooltipAndAccesskeyAttribs 689 */ 690 public function testTooltipAndAccesskeyAttribs( $name, $msgParams, $options, $expected ) { 691 $this->setMwGlobals( [ 692 'wgWatchlistExpiry' => true, 693 ] ); 694 $user = $this->createMock( User::class ); 695 $user->method( 'isRegistered' )->willReturn( true ); 696 $user->method( 'isLoggedIn' )->willReturn( true ); 697 698 $title = SpecialPage::getTitleFor( 'Blankpage' ); 699 700 $context = RequestContext::getMain(); 701 $context->setTitle( $title ); 702 $context->setUser( $user ); 703 704 $watchedItemWithoutExpiry = new WatchedItem( $user, $title, null, null ); 705 706 $result = Linker::tooltipAndAccesskeyAttribs( $name, $msgParams, $options ); 707 708 $this->assertEquals( $expected, $result ); 709 } 710} 711