Merge "docs: Fix typo"
[mediawiki.git] / tests / phpunit / includes / page / ArticleViewTest.php
blob1dabbeb8d00ec452729a6564a43875530119a913
1 <?php
3 use MediaWiki\CommentStore\CommentStoreComment;
4 use MediaWiki\Content\Content;
5 use MediaWiki\Content\ContentHandler;
6 use MediaWiki\Content\WikitextContent;
7 use MediaWiki\Context\DerivativeContext;
8 use MediaWiki\Context\RequestContext;
9 use MediaWiki\MainConfigNames;
10 use MediaWiki\Output\OutputPage;
11 use MediaWiki\Parser\ParserOutput;
12 use MediaWiki\Request\FauxRequest;
13 use MediaWiki\Revision\MutableRevisionRecord;
14 use MediaWiki\Revision\RevisionRecord;
15 use MediaWiki\Revision\SlotRecord;
16 use MediaWiki\Status\Status;
17 use MediaWiki\Title\Title;
18 use MediaWiki\User\User;
19 use MediaWiki\Utils\MWTimestamp;
20 use PHPUnit\Framework\MockObject\MockObject;
21 use Wikimedia\TestingAccessWrapper;
23 /**
24 * @covers \Article::view()
25 * @group Database
27 class ArticleViewTest extends MediaWikiIntegrationTestCase {
29 protected function setUp(): void {
30 parent::setUp();
32 $this->setUserLang( 'qqx' );
35 private function getHtml( OutputPage $output ) {
36 return preg_replace( '/<!--.*?-->/s', '', $output->getHTML() );
39 /**
40 * @param string|Title $title
41 * @param Content[]|string[] $revisionContents Content of the revisions to create
42 * (as Content or string).
43 * @param RevisionRecord[] &$revisions will be filled with the RevisionRecord for $content.
45 * @return WikiPage
47 private function getPage( $title, array $revisionContents = [], array &$revisions = [] ) {
48 if ( is_string( $title ) ) {
49 $title = Title::makeTitle( $this->getDefaultWikitextNS(), $title );
52 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
54 $user = $this->getTestUser()->getUser();
56 // Make sure all revision have different timestamps all the time,
57 // to make timestamp asserts below deterministic.
58 $time = time() - 86400;
59 MWTimestamp::setFakeTime( $time );
61 foreach ( $revisionContents as $key => $cont ) {
62 if ( is_string( $cont ) ) {
63 $cont = new WikitextContent( $cont );
66 $rev = $page->newPageUpdater( $user )
67 ->setContent( SlotRecord::MAIN, $cont )
68 ->saveRevision( CommentStoreComment::newUnsavedComment( 'Rev ' . $key ) );
70 $revisions[ $key ] = $rev;
71 MWTimestamp::setFakeTime( ++$time );
73 MWTimestamp::setFakeTime( false );
75 // Clear content model cache to support tests that mock the revision
76 $this->getServiceContainer()->getMainWANObjectCache()->clearProcessCache();
78 return $page;
81 /**
82 * @covers \Article::getOldId()
83 * @covers \Article::getRevIdFetched()
85 public function testGetOldId() {
86 $revisions = [];
87 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
89 $idA = $revisions[1]->getId();
90 $idB = $revisions[2]->getId();
92 // oldid in constructor
93 $article = new Article( $page->getTitle(), $idA );
94 $this->assertSame( $idA, $article->getOldID() );
95 $article->fetchRevisionRecord();
96 $this->assertSame( $idA, $article->getRevIdFetched() );
98 // oldid 0 in constructor
99 $article = new Article( $page->getTitle(), 0 );
100 $this->assertSame( 0, $article->getOldID() );
101 $article->fetchRevisionRecord();
102 $this->assertSame( $idB, $article->getRevIdFetched() );
104 // oldid in request
105 $article = new Article( $page->getTitle() );
106 $context = new RequestContext();
107 $context->setRequest( new FauxRequest( [ 'oldid' => $idA ] ) );
108 $article->setContext( $context );
109 $this->assertSame( $idA, $article->getOldID() );
110 $article->fetchRevisionRecord();
111 $this->assertSame( $idA, $article->getRevIdFetched() );
113 // no oldid
114 $article = new Article( $page->getTitle() );
115 $context = new RequestContext();
116 $context->setRequest( new FauxRequest( [] ) );
117 $article->setContext( $context );
118 $this->assertSame( 0, $article->getOldID() );
119 $article->fetchRevisionRecord();
120 $this->assertSame( $idB, $article->getRevIdFetched() );
123 public function testView() {
124 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ] );
126 $article = new Article( $page->getTitle(), 0 );
127 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
128 $article->view();
130 $output = $article->getContext()->getOutput();
131 $this->assertStringContainsString( 'Test B', $this->getHtml( $output ) );
132 $this->assertStringNotContainsString( 'id="mw-revision-info"', $this->getHtml( $output ) );
133 $this->assertStringNotContainsString( 'id="mw-revision-nav"', $this->getHtml( $output ) );
136 public function testViewCached() {
137 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ] );
139 $po = new ParserOutput( 'Cached Text' );
141 $article = new Article( $page->getTitle(), 0 );
142 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
144 $cache = $this->getServiceContainer()->getParserCache();
145 $cache->save( $po, $page, $article->getParserOptions() );
147 $article->view();
149 $output = $article->getContext()->getOutput();
150 $this->assertStringContainsString( 'Cached Text', $this->getHtml( $output ) );
151 $this->assertStringNotContainsString( 'Test A', $this->getHtml( $output ) );
152 $this->assertStringNotContainsString( 'Test B', $this->getHtml( $output ) );
156 * @covers \Article::getPage
157 * @covers \WikiPage::getRedirectTarget
158 * @covers \MediaWiki\Page\RedirectLookup::getRedirectTarget
160 public function testViewRedirect() {
161 $target = Title::makeTitle( $this->getDefaultWikitextNS(), 'Test_Target' );
162 $redirectText = '#REDIRECT [[' . $target->getPrefixedText() . ']]';
164 $page = $this->getPage( __METHOD__, [ $redirectText ] );
166 $article = new Article( $page->getTitle(), 0 );
167 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
168 $article->view();
170 $redirectStore = $this->getServiceContainer()->getRedirectStore();
171 $titleFormatter = $this->getServiceContainer()->getTitleFormatter();
173 $this->assertNotNull(
174 $redirectStore->getRedirectTarget( $article->getPage() )
176 $this->assertSame(
177 $target->getPrefixedDBkey(),
178 $titleFormatter->getPrefixedDBkey( $redirectStore->getRedirectTarget( $article->getPage() ) )
181 $output = $article->getContext()->getOutput();
182 $this->assertStringContainsString( 'class="redirectText"', $this->getHtml( $output ) );
183 $this->assertStringContainsString(
184 '>' . htmlspecialchars( $target->getPrefixedText() ) . '<',
185 $this->getHtml( $output )
189 public function testViewNonText() {
190 $dummy = $this->getPage( __METHOD__, [ 'Dummy' ] );
191 $dummyRev = $dummy->getRevisionRecord();
192 $title = $dummy->getTitle();
194 /** @var MockObject|ContentHandler $mockHandler */
195 $mockHandler = $this->getMockBuilder( ContentHandler::class )
196 ->onlyMethods(
198 'isParserCacheSupported',
199 'serializeContent',
200 'unserializeContent',
201 'makeEmptyContent',
202 'getParserOutput',
205 ->setConstructorArgs( [ 'NotText', [ 'application/frobnitz' ] ] )
206 ->getMock();
208 $mockHandler->method( 'isParserCacheSupported' )
209 ->willReturn( false );
210 $mockHandler->method( 'getParserOutput' )
211 ->willReturn( new ParserOutput( 'Structured Output' ) );
213 $this->setTemporaryHook(
214 'ContentHandlerForModelID',
215 static function ( $id, &$handler ) use ( $mockHandler ) {
216 $handler = $mockHandler;
220 /** @var MockObject|Content $content */
221 $content = $this->createMock( Content::class );
222 $content->method( 'getModel' )
223 ->willReturn( 'NotText' );
224 $content->expects( $this->never() )->method( 'getNativeData' );
225 $content->method( 'copy' )->willReturnSelf();
227 $rev = new MutableRevisionRecord( $title );
228 $rev->setId( $dummyRev->getId() );
229 $rev->setPageId( $title->getArticleID() );
230 $rev->setUser( $dummyRev->getUser() );
231 $rev->setComment( $dummyRev->getComment() );
232 $rev->setTimestamp( $dummyRev->getTimestamp() );
234 $rev->setContent( SlotRecord::MAIN, $content );
236 /** @var MockObject|WikiPage $page */
237 $page = $this->getMockBuilder( WikiPage::class )
238 ->onlyMethods( [ 'getRevisionRecord', 'getLatest', 'getContentHandler' ] )
239 ->setConstructorArgs( [ $title ] )
240 ->getMock();
242 $page->method( 'getRevisionRecord' )
243 ->willReturn( $rev );
244 $page->method( 'getLatest' )
245 ->willReturn( $rev->getId() );
246 $page->method( 'getContentHandler' )
247 ->willReturn( $mockHandler );
249 $article = Article::newFromWikiPage( $page, RequestContext::getMain() );
250 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
251 $article->view();
253 $output = $article->getContext()->getOutput();
254 $this->assertStringContainsString( 'Structured Output', $this->getHtml( $output ) );
255 $this->assertStringNotContainsString( 'Dummy', $this->getHtml( $output ) );
258 public function testViewOfOldRevision() {
259 $revisions = [];
260 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
261 $idA = $revisions[1]->getId();
263 $article = new Article( $page->getTitle(), $idA );
264 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
265 $article->view();
267 $output = $article->getContext()->getOutput();
268 $this->assertStringContainsString( 'Test A', $this->getHtml( $output ) );
269 $this->assertStringContainsString( 'id="mw-revision-info"', $output->getSubtitle() );
270 $this->assertStringContainsString( 'id="mw-revision-nav"', $output->getSubtitle() );
272 $this->assertStringNotContainsString( 'id="revision-info-current"', $output->getSubtitle() );
273 $this->assertStringNotContainsString( 'Test B', $this->getHtml( $output ) );
274 $this->assertSame( $idA, $output->getRevisionId() );
275 $this->assertSame( $revisions[1]->getTimestamp(), $output->getRevisionTimestamp() );
278 public function testViewOfOldRevisionFromCache() {
279 $this->overrideConfigValues( [
280 MainConfigNames::OldRevisionParserCacheExpireTime => 100500,
281 MainConfigNames::MainCacheType => CACHE_HASH,
282 ] );
284 $revisions = [];
285 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
286 $idA = $revisions[1]->getId();
287 $context = RequestContext::getMain();
288 $context->setTitle( $page->getTitle() );
290 // View the revision once (to get it into the cache)
291 $article = new Article( $page->getTitle(), $idA );
292 $article->view();
294 // Reset the output page and view the revision again (from ParserCache)
295 $article = new Article( $page->getTitle(), $idA );
296 $context->setOutput( new OutputPage( $context ) );
297 $article->setContext( $context );
299 $outputPageBeforeHTMLRevisionId = null;
300 $this->setTemporaryHook( 'OutputPageBeforeHTML',
301 static function ( OutputPage $out ) use ( &$outputPageBeforeHTMLRevisionId ) {
302 $outputPageBeforeHTMLRevisionId = $out->getRevisionId();
306 $article->view();
307 $output = $article->getContext()->getOutput();
308 $this->assertStringContainsString( 'Test A', $this->getHtml( $output ) );
309 $this->assertSame( 1, substr_count( $output->getSubtitle(), 'cdx-message--warning' ) );
310 $this->assertSame( $idA, $output->getRevisionId() );
311 $this->assertSame( $idA, $outputPageBeforeHTMLRevisionId );
312 $this->assertSame( $revisions[1]->getTimestamp(), $output->getRevisionTimestamp() );
315 public function testViewOfCurrentRevision() {
316 $revisions = [];
317 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
318 $idB = $revisions[2]->getId();
320 $article = new Article( $page->getTitle(), $idB );
321 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
322 $article->view();
324 $output = $article->getContext()->getOutput();
325 $this->assertStringContainsString( 'Test B', $this->getHtml( $output ) );
326 $this->assertStringContainsString( 'id="mw-revision-info-current"', $output->getSubtitle() );
327 $this->assertStringContainsString( 'id="mw-revision-nav"', $output->getSubtitle() );
330 public function testViewOfCurrentRevisionDirty() {
331 $this->overrideConfigValue(
332 MainConfigNames::PoolCounterConf,
334 'ArticleView' => [
335 'class' => MockPoolCounterFailing::class,
340 $revisions = [];
341 $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ], $revisions );
342 $idA = $revisions[1]->getId();
344 // Do the next edit without ParserCache produce an outdated cache entry
345 $parserCacheFactory = $this->getServiceContainer()->getParserCacheFactory();
346 $this->overrideConfigValue( MainConfigNames::ParserCacheType, CACHE_NONE );
347 $latestEditStatus = $this->editPage( $page, 'Test B' );
348 // Restore the old cache instance with the now outdated cache entry
349 $this->setService( 'ParserCacheFactory', $parserCacheFactory );
351 // Request the article for the latest
352 $article = new Article( $page->getTitle() );
353 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
354 $article->view();
356 // Expected the old values to return
357 $output = $article->getContext()->getOutput();
358 $this->assertStringContainsString( 'Test A', $this->getHtml( $output ) );
359 $this->assertSame( $idA, $output->getRevisionId() );
360 $this->assertSame( $revisions[1]->getTimestamp(), $output->getRevisionTimestamp() );
363 public function testViewOfMissingRevision() {
364 $revisions = [];
365 $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ], $revisions );
366 $badId = $revisions[1]->getId() + 100;
368 $article = new Article( $page->getTitle(), $badId );
369 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
370 $article->view();
372 $output = $article->getContext()->getOutput();
373 $this->assertStringContainsString( 'missing-revision: ' . $badId, $this->getHtml( $output ) );
375 $this->assertStringNotContainsString( 'Test A', $this->getHtml( $output ) );
378 public function testViewOfDeletedRevision() {
379 $revisions = [];
380 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
381 $idA = $revisions[1]->getId();
383 $revDelList = $this->getRevDelRevisionList( $page->getTitle(), $idA );
384 $revDelList->setVisibility( [
385 'value' => [ RevisionRecord::DELETED_TEXT => 1 ],
386 'comment' => "Testing",
387 ] );
389 $article = new Article( $page->getTitle(), $idA );
390 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
391 $article->view();
393 $output = $article->getContext()->getOutput();
394 $this->assertStringContainsString( 'rev-deleted-text-permission', $this->getHtml( $output ) );
396 $this->assertStringNotContainsString( 'Test A', $this->getHtml( $output ) );
397 $this->assertStringNotContainsString( 'Test B', $this->getHtml( $output ) );
400 public function testUnhiddenViewOfDeletedRevision() {
401 $revisions = [];
402 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
403 $idA = $revisions[1]->getId();
405 $revDelList = $this->getRevDelRevisionList( $page->getTitle(), $idA );
406 $revDelList->setVisibility( [
407 'value' => [
408 RevisionRecord::DELETED_TEXT => 1,
409 RevisionRecord::DELETED_COMMENT => 1,
410 RevisionRecord::DELETED_USER => 1,
412 'comment' => "Testing",
413 ] );
415 $realContext = RequestContext::getMain();
416 $oldUser = $realContext->getUser();
417 $oldLanguage = $realContext->getLanguage();
419 $article = new Article( $page->getTitle(), $idA );
420 $context = new DerivativeContext( $realContext );
421 $article->setContext( $context );
422 $context->getOutput()->setTitle( $page->getTitle() );
423 $context->getRequest()->setVal( 'unhide', 1 );
424 $context->setUser( $this->getTestUser( [ 'sysop' ] )->getUser() );
426 // Need global user set to sysop, global state in Linker::revUserTools/Linker::revComment (T309479)
427 $realContext->setUser( $context->getUser() );
428 // Language is resetted in setUser
429 $this->setUserLang( $oldLanguage );
431 $article->view();
433 $output = $article->getContext()->getOutput();
434 $subtitle = $output->getSubtitle();
435 $html = $this->getHtml( $output );
437 // Test that oldid is select, not the current version
438 $this->assertStringNotContainsString( 'Test B', $html );
440 // Warning about rev-del must exists
441 $this->assertStringContainsString( 'rev-deleted-text-view', $html );
443 // Test for the hidden values
444 $this->assertStringContainsString( 'Test A', $html );
445 $this->assertStringContainsString( $revisions[1]->getUser()->getName(), $subtitle );
446 $this->assertStringContainsString( '(parentheses: Rev 1)', $subtitle );
448 // Should not contain the rev-del messages
449 $this->assertStringNotContainsString( '(rev-deleted-user)', $subtitle );
450 $this->assertStringNotContainsString( '(rev-deleted-comment)', $subtitle );
452 $realContext->setUser( $oldUser );
455 public function testHiddenViewOfDeletedRevision() {
456 $revisions = [];
457 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
458 $idA = $revisions[1]->getId();
460 $revDelList = $this->getRevDelRevisionList( $page->getTitle(), $idA );
461 $revDelList->setVisibility( [
462 'value' => [
463 RevisionRecord::DELETED_TEXT => 1,
464 RevisionRecord::DELETED_COMMENT => 1,
465 RevisionRecord::DELETED_USER => 1,
467 'comment' => "Testing",
468 ] );
470 $realContext = RequestContext::getMain();
471 $oldUser = $realContext->getUser();
472 $oldLanguage = $realContext->getLanguage();
474 $article = new Article( $page->getTitle(), $idA );
475 $context = new DerivativeContext( $realContext );
476 $article->setContext( $context );
477 $context->getOutput()->setTitle( $page->getTitle() );
478 // No unhide=1 is set in this test case
479 $context->setUser( $this->getTestUser( [ 'sysop' ] )->getUser() );
481 // Need global user set to sysop, global state in Linker::revUserTools/Linker::revComment (T309479)
482 $realContext->setUser( $context->getUser() );
483 // Language is resetted in setUser
484 $this->setUserLang( $oldLanguage );
486 $article->view();
488 $output = $article->getContext()->getOutput();
489 $subtitle = $output->getSubtitle();
490 $html = $this->getHtml( $output );
492 // Test that oldid is select, not the current version
493 $this->assertStringNotContainsString( 'Test B', $html );
495 // Warning about rev-del must exists
496 $this->assertStringContainsString( 'rev-deleted-text-unhide', $html );
498 // Test for the rev-del messages
499 $this->assertStringContainsString( '(rev-deleted-user)', $subtitle );
500 $this->assertStringContainsString( '(rev-deleted-comment)', $subtitle );
502 // Should not contain the hidden values
503 $this->assertStringNotContainsString( 'Test A', $html );
504 $this->assertStringNotContainsString( $revisions[1]->getUser()->getName(), $subtitle );
505 $this->assertStringNotContainsString( '(parentheses: Rev 1)', $subtitle );
507 $realContext->setUser( $oldUser );
510 public function testViewMissingPage() {
511 $page = $this->getPage( __METHOD__ );
513 $article = new Article( $page->getTitle() );
514 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
515 $article->view();
517 $output = $article->getContext()->getOutput();
518 $this->assertStringContainsString( '(noarticletextanon)', $this->getHtml( $output ) );
521 public function testViewDeletedPage() {
522 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ] );
523 $this->deletePage( $page );
525 $article = new Article( $page->getTitle() );
526 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
527 $article->view();
529 $output = $article->getContext()->getOutput();
530 $this->assertStringContainsString( 'moveddeleted', $this->getHtml( $output ) );
531 $this->assertStringContainsString( 'logentry-delete-delete', $this->getHtml( $output ) );
532 $this->assertStringContainsString( '(noarticletextanon)', $this->getHtml( $output ) );
534 $this->assertStringNotContainsString( 'Test A', $this->getHtml( $output ) );
535 $this->assertStringNotContainsString( 'Test B', $this->getHtml( $output ) );
538 public function testViewMessagePage() {
539 $title = Title::makeTitle( NS_MEDIAWIKI, 'Mainpage' );
540 $page = $this->getPage( $title );
542 $article = new Article( $page->getTitle() );
543 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
544 $article->view();
546 $output = $article->getContext()->getOutput();
547 $this->assertStringContainsString(
548 wfMessage( 'mainpage' )->inContentLanguage()->parse(),
549 $this->getHtml( $output )
551 $this->assertStringNotContainsString( '(noarticletextanon)', $this->getHtml( $output ) );
554 public function testViewMissingUserPage() {
555 $user = $this->getTestUser()->getUser();
556 $user->addToDatabase();
558 $title = Title::makeTitle( NS_USER, $user->getName() );
560 $page = $this->getPage( $title );
562 $article = new Article( $page->getTitle() );
563 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
564 $article->view();
566 $output = $article->getContext()->getOutput();
567 $this->assertStringContainsString( '(noarticletextanon)', $this->getHtml( $output ) );
568 $this->assertStringNotContainsString(
569 '(userpage-userdoesnotexist-view)',
570 $this->getHtml( $output )
574 public function testViewUserPageOfNonexistingUser() {
575 $user = User::newFromName( 'Testing ' . __METHOD__ );
577 $title = Title::makeTitle( NS_USER, $user->getName() );
579 $page = $this->getPage( $title );
581 $article = new Article( $page->getTitle() );
582 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
583 $article->view();
585 $output = $article->getContext()->getOutput();
586 $this->assertStringContainsString( '(noarticletextanon)', $this->getHtml( $output ) );
587 $this->assertStringContainsString(
588 '(userpage-userdoesnotexist-view:',
589 $this->getHtml( $output )
593 public function testArticleViewHeaderHook() {
594 $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ] );
596 $article = new Article( $page->getTitle(), 0 );
597 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
599 $this->setTemporaryHook(
600 'ArticleViewHeader',
601 function ( Article $articlePage, &$outputDone, &$useParserCache ) use ( $article ) {
602 $this->assertSame( $article, $articlePage, '$articlePage' );
604 $outputDone = new ParserOutput( 'Hook Text' );
605 $outputDone->setTitleText( 'Hook Title' );
607 $articlePage->getContext()->getOutput()->addParserOutput( $outputDone );
611 $article->view();
613 $output = $article->getContext()->getOutput();
614 $this->assertStringNotContainsString( 'Test A', $this->getHtml( $output ) );
615 $this->assertStringContainsString( 'Hook Text', $this->getHtml( $output ) );
616 $this->assertSame( 'Hook Title', $output->getPageTitle() );
619 public function testArticleRevisionViewCustomHook() {
620 $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ] );
622 $article = new Article( $page->getTitle(), 0 );
623 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
625 // use ArticleViewHeader hook to bypass the parser cache
626 $this->setTemporaryHook(
627 'ArticleViewHeader',
628 static function ( Article $articlePage, &$outputDone, &$useParserCache ) {
629 $useParserCache = false;
633 $this->setTemporaryHook(
634 'ArticleRevisionViewCustom',
635 function ( RevisionRecord $rev, Title $title, $oldid, OutputPage $output ) use ( $page ) {
636 $content = $rev->getContent( SlotRecord::MAIN );
637 $this->assertSame( $page->getTitle(), $title, '$title' );
638 $this->assertSame( 'Test A', $content->getText(), '$content' );
640 $output->addHTML( 'Hook Text' );
641 return false;
645 $article->view();
647 $output = $article->getContext()->getOutput();
648 $this->assertStringNotContainsString( 'Test A', $this->getHtml( $output ) );
649 $this->assertStringContainsString( 'Hook Text', $this->getHtml( $output ) );
652 public function testShowMissingArticleHook() {
653 $page = $this->getPage( __METHOD__ );
655 $article = new Article( $page->getTitle() );
656 $article->getContext()->getOutput()->setTitle( $page->getTitle() );
658 $this->setTemporaryHook(
659 'ShowMissingArticle',
660 function ( Article $articlePage ) use ( $article ) {
661 $this->assertSame( $article, $articlePage, '$articlePage' );
663 $articlePage->getContext()->getOutput()->addHTML( 'Hook Text' );
667 $article->view();
669 $output = $article->getContext()->getOutput();
670 $this->assertStringContainsString( '(noarticletextanon)', $this->getHtml( $output ) );
671 $this->assertStringContainsString( 'Hook Text', $this->getHtml( $output ) );
675 * @covers \Article::showViewError()
677 public function testViewLatestError() {
678 $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ] );
680 $article = new Article( $page->getTitle(), 0 );
681 $output = $article->getContext()->getOutput();
682 $output->setTitle( $page->getTitle() );
684 // use ArticleViewHeader hook to bypass the parser cache
685 $this->setTemporaryHook(
686 'ArticleViewHeader',
687 static function ( Article $articlePage, &$outputDone, &$useParserCache ) {
688 $useParserCache = false;
692 $article = TestingAccessWrapper::newFromObject( $article );
693 $article->fetchResult = Status::newFatal(
694 'rev-deleted-text-permission',
695 $page->getTitle()->getPrefixedDBkey()
698 $article->view();
700 $this->assertStringContainsString(
701 'rev-deleted-text-permission: ArticleViewTest::testViewLatestError',
702 $this->getHtml( $output )
707 * @covers \Article::showViewError()
709 public function testViewOldError() {
710 $revisions = [];
711 $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
712 $idA = $revisions[1]->getId();
714 $article = new Article( $page->getTitle(), $idA );
715 $output = $article->getContext()->getOutput();
716 $output->setTitle( $page->getTitle() );
718 $article = TestingAccessWrapper::newFromObject( $article );
719 $article->fetchResult = Status::newFatal(
720 'rev-deleted-text-permission',
721 $page->getTitle()->getPrefixedDBkey()
724 $article->view();
726 $this->assertStringContainsString(
727 'rev-deleted-text-permission: ArticleViewTest::testViewOldError',
728 $this->getHtml( $output )
732 private function getRevDelRevisionList( $title, $revisionId ) {
733 $services = $this->getServiceContainer();
734 $context = new DerivativeContext( RequestContext::getMain() );
735 $context->setUser(
736 $this->getTestUser( [ 'sysop' ] )->getUser()
738 return new RevDelRevisionList(
739 $context,
740 $title,
741 [ $revisionId ],
742 $services->getConnectionProvider(),
743 $services->getHookContainer(),
744 $services->getHtmlCacheUpdater(),
745 $services->getRevisionStore()
750 * Test the "useParsoid" parser option and the ArticleParserOptions
751 * hook.
753 public function testUseParsoid() {
754 // Create an appropriate test page.
755 $title = Title::makeTitle( NS_MAIN, 'UseParsoidTest' );
756 $article = new Article( $title );
757 $page = $this->getExistingTestPage( $title );
758 $page->doUserEditContent(
759 ContentHandler::makeContent(
760 '[[Foo]]',
761 $title,
762 // Force this page to be wikitext
763 CONTENT_MODEL_WIKITEXT
765 $this->getTestSysop()->getUser(),
766 'TestUseParsoid Summary',
767 EDIT_SUPPRESS_RC
769 $article->view();
770 $html = $this->getHtml( $article->getContext()->getOutput() );
771 // Confirm that this is NOT parsoid-generated HTML
772 $this->assertStringNotContainsString(
773 'rel="mw:WikiLink"',
774 $html
777 // Now enable Parsoid via the ArticleParserOptions hook
778 $article = new Article( $title );
779 $this->setTemporaryHook( 'ArticleParserOptions', static function ( $article, $popts ) {
780 $popts->setUseParsoid();
781 } );
782 $article->view();
783 $html = $this->getHtml( $article->getContext()->getOutput() );
784 // Look for a marker that this is Parsoid-generated HTML
785 $this->assertStringContainsString(
786 'rel="mw:WikiLink"',
787 $html