MediaWiki REL1_32
RenderedRevisionTest.php
Go to the documentation of this file.
1<?php
2
4
5use Content;
6use Language;
19use ParserOutput;
20use PHPUnit\Framework\MockObject\MockObject;
21use Title;
22use User;
23use Wikimedia\TestingAccessWrapper;
25
30
33
34 public function setUp() {
35 parent::setUp();
36
37 $this->combinerCallback = function ( RenderedRevision $rr, array $hints = [] ) {
38 return $this->combineOutput( $rr, $hints );
39 };
40 }
41
42 private function combineOutput( RenderedRevision $rrev, array $hints = [] ) {
43 // NOTE: the is a slightly simplified version of RevisionRenderer::combineSlotOutput
44
45 $withHtml = $hints['generate-html'] ?? true;
46
47 $revision = $rrev->getRevision();
48 $slots = $revision->getSlots()->getSlots();
49
50 $combinedOutput = new ParserOutput( null );
51 $slotOutput = [];
52 foreach ( $slots as $role => $slot ) {
53 $out = $rrev->getSlotParserOutput( $role, $hints );
54 $slotOutput[$role] = $out;
55
56 $combinedOutput->mergeInternalMetaDataFrom( $out );
57 $combinedOutput->mergeTrackingMetaDataFrom( $out );
58 }
59
60 if ( $withHtml ) {
61 $html = '';
63 foreach ( $slotOutput as $role => $out ) {
64
65 if ( $html !== '' ) {
66 // skip header for the first slot
67 $html .= "(($role))";
68 }
69
70 $html .= $out->getRawText();
71 $combinedOutput->mergeHtmlMetaDataFrom( $out );
72 }
73
74 $combinedOutput->setText( $html );
75 }
76
77 return $combinedOutput;
78 }
79
85 private function getMockTitle( $articleId, $revisionId ) {
87 $mock = $this->getMockBuilder( Title::class )
88 ->disableOriginalConstructor()
89 ->getMock();
90 $mock->expects( $this->any() )
91 ->method( 'getNamespace' )
92 ->will( $this->returnValue( NS_MAIN ) );
93 $mock->expects( $this->any() )
94 ->method( 'getText' )
95 ->will( $this->returnValue( 'RenderTestPage' ) );
96 $mock->expects( $this->any() )
97 ->method( 'getPrefixedText' )
98 ->will( $this->returnValue( 'RenderTestPage' ) );
99 $mock->expects( $this->any() )
100 ->method( 'getDBkey' )
101 ->will( $this->returnValue( 'RenderTestPage' ) );
102 $mock->expects( $this->any() )
103 ->method( 'getArticleID' )
104 ->will( $this->returnValue( $articleId ) );
105 $mock->expects( $this->any() )
106 ->method( 'getLatestRevId' )
107 ->will( $this->returnValue( $revisionId ) );
108 $mock->expects( $this->any() )
109 ->method( 'getContentModel' )
110 ->will( $this->returnValue( CONTENT_MODEL_WIKITEXT ) );
111 $mock->expects( $this->any() )
112 ->method( 'getPageLanguage' )
113 ->will( $this->returnValue( Language::factory( 'en' ) ) );
114 $mock->expects( $this->any() )
115 ->method( 'isContentPage' )
116 ->will( $this->returnValue( true ) );
117 $mock->expects( $this->any() )
118 ->method( 'equals' )
119 ->willReturnCallback( function ( Title $other ) use ( $mock ) {
120 return $mock->getPrefixedText() === $other->getPrefixedText();
121 } );
122 $mock->expects( $this->any() )
123 ->method( 'userCan' )
124 ->willReturnCallback( function ( $perm, User $user ) use ( $mock ) {
125 return $user->isAllowed( $perm );
126 } );
127
128 return $mock;
129 }
130
138 private function getMockRevision(
139 $class,
140 $title,
141 $id = null,
142 $visibility = 0,
143 array $content = null
144 ) {
145 $frank = new UserIdentityValue( 9, 'Frank', 0 );
146
147 if ( !$content ) {
148 $text = "";
149 $text .= "* page:{{PAGENAME}}!\n";
150 $text .= "* rev:{{REVISIONID}}!\n";
151 $text .= "* user:{{REVISIONUSER}}!\n";
152 $text .= "* time:{{REVISIONTIMESTAMP}}!\n";
153 $text .= "* [[Link It]]\n";
154
155 $content = [ 'main' => new WikitextContent( $text ) ];
156 }
157
159 $mock = $this->getMockBuilder( $class )
160 ->disableOriginalConstructor()
161 ->setMethods( [
162 'getId',
163 'getPageId',
164 'getPageAsLinkTarget',
165 'getUser',
166 'getVisibility',
167 'getTimestamp',
168 ] )->getMock();
169
170 $mock->method( 'getId' )->willReturn( $id );
171 $mock->method( 'getPageId' )->willReturn( $title->getArticleID() );
172 $mock->method( 'getPageAsLinkTarget' )->willReturn( $title );
173 $mock->method( 'getUser' )->willReturn( $frank );
174 $mock->method( 'getVisibility' )->willReturn( $visibility );
175 $mock->method( 'getTimestamp' )->willReturn( '20180101000003' );
176
178 $mockAccess = TestingAccessWrapper::newFromObject( $mock );
179 $mockAccess->mSlots = new MutableRevisionSlots();
180
181 foreach ( $content as $role => $cnt ) {
182 $mockAccess->mSlots->setContent( $role, $cnt );
183 }
184
185 return $mock;
186 }
187
189 $title = $this->getMockTitle( 0, 21 );
190 $rev = $this->getMockRevision( RevisionStoreRecord::class, $title );
191
192 $options = ParserOptions::newCanonical( 'canonical' );
193 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
194
195 $this->assertFalse( $rr->isContentDeleted(), 'isContentDeleted' );
196
197 $this->assertSame( $rev, $rr->getRevision() );
198 $this->assertSame( $options, $rr->getOptions() );
199
200 $html = $rr->getRevisionParserOutput()->getText();
201
202 $this->assertContains( 'page:RenderTestPage!', $html );
203 $this->assertContains( 'user:Frank!', $html );
204 $this->assertContains( 'time:20180101000003!', $html );
205 }
206
208 $title = $this->getMockTitle( 0, 21 );
209 $name = $title->getPrefixedText();
210
211 $text = "(ONE)<includeonly>(TWO)</includeonly><noinclude>#{{:$name}}#</noinclude>";
212
213 $content = [
214 'main' => new WikitextContent( $text )
215 ];
216
217 $rev = $this->getMockRevision( RevisionStoreRecord::class, $title, null, 0, $content );
218
219 $options = ParserOptions::newCanonical( 'canonical' );
220 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
221
222 $html = $rr->getRevisionParserOutput()->getText();
223 $this->assertContains( '(ONE)#(ONE)(TWO)#', $html );
224 }
225
227 $title = $this->getMockTitle( 7, 21 );
228 $rev = $this->getMockRevision( RevisionStoreRecord::class, $title, 21 );
229
230 $options = ParserOptions::newCanonical( 'canonical' );
231 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
232
233 $this->assertFalse( $rr->isContentDeleted(), 'isContentDeleted' );
234
235 $this->assertSame( $rev, $rr->getRevision() );
236 $this->assertSame( $options, $rr->getOptions() );
237
238 $html = $rr->getRevisionParserOutput()->getText();
239
240 $this->assertContains( 'page:RenderTestPage!', $html );
241 $this->assertContains( 'rev:21!', $html );
242 $this->assertContains( 'user:Frank!', $html );
243 $this->assertContains( 'time:20180101000003!', $html );
244
245 $this->assertSame( $html, $rr->getSlotParserOutput( SlotRecord::MAIN )->getText() );
246 }
247
249 $title = $this->getMockTitle( 7, 21 );
250 $rev = $this->getMockRevision( RevisionStoreRecord::class, $title, 11 );
251
252 $options = ParserOptions::newCanonical( 'canonical' );
253 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
254
255 $this->assertFalse( $rr->isContentDeleted(), 'isContentDeleted' );
256
257 $this->assertSame( $rev, $rr->getRevision() );
258 $this->assertSame( $options, $rr->getOptions() );
259
260 $html = $rr->getRevisionParserOutput()->getText();
261
262 $this->assertContains( 'page:RenderTestPage!', $html );
263 $this->assertContains( 'rev:11!', $html );
264 $this->assertContains( 'user:Frank!', $html );
265 $this->assertContains( 'time:20180101000003!', $html );
266
267 $this->assertSame( $html, $rr->getSlotParserOutput( SlotRecord::MAIN )->getText() );
268 }
269
271 $title = $this->getMockTitle( 7, 21 );
272 $rev = $this->getMockRevision( RevisionArchiveRecord::class, $title, 11 );
273
274 $options = ParserOptions::newCanonical( 'canonical' );
275 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
276
277 $this->assertFalse( $rr->isContentDeleted(), 'isContentDeleted' );
278
279 $this->assertSame( $rev, $rr->getRevision() );
280 $this->assertSame( $options, $rr->getOptions() );
281
282 $html = $rr->getRevisionParserOutput()->getText();
283
284 $this->assertContains( 'page:RenderTestPage!', $html );
285 $this->assertContains( 'rev:11!', $html );
286 $this->assertContains( 'user:Frank!', $html );
287 $this->assertContains( 'time:20180101000003!', $html );
288
289 $this->assertSame( $html, $rr->getSlotParserOutput( SlotRecord::MAIN )->getText() );
290 }
291
293 $title = $this->getMockTitle( 7, 21 );
294 $rev = $this->getMockRevision(
295 RevisionStoreRecord::class,
296 $title,
297 11,
298 RevisionRecord::DELETED_TEXT
299 );
300
301 $options = ParserOptions::newCanonical( 'canonical' );
302 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
303
304 $this->setExpectedException( SuppressedDataException::class );
305 $rr->getRevisionParserOutput();
306 }
307
309 $title = $this->getMockTitle( 7, 21 );
310 $rev = $this->getMockRevision(
311 RevisionStoreRecord::class,
312 $title,
313 11,
314 RevisionRecord::DELETED_TEXT
315 );
316
317 $options = ParserOptions::newCanonical( 'canonical' );
318 $sysop = $this->getTestUser( [ 'sysop' ] )->getUser(); // privileged!
319 $rr = new RenderedRevision(
320 $title,
321 $rev,
322 $options,
323 $this->combinerCallback,
324 RevisionRecord::FOR_THIS_USER,
325 $sysop
326 );
327
328 $this->assertTrue( $rr->isContentDeleted(), 'isContentDeleted' );
329
330 $this->assertSame( $rev, $rr->getRevision() );
331 $this->assertSame( $options, $rr->getOptions() );
332
333 $html = $rr->getRevisionParserOutput()->getText();
334
335 // Suppressed content should be visible for sysops
336 $this->assertContains( 'page:RenderTestPage!', $html );
337 $this->assertContains( 'rev:11!', $html );
338 $this->assertContains( 'user:Frank!', $html );
339 $this->assertContains( 'time:20180101000003!', $html );
340
341 $this->assertSame( $html, $rr->getSlotParserOutput( SlotRecord::MAIN )->getText() );
342 }
343
345 $title = $this->getMockTitle( 7, 21 );
346 $rev = $this->getMockRevision(
347 RevisionStoreRecord::class,
348 $title,
349 11,
350 RevisionRecord::DELETED_TEXT
351 );
352
353 $options = ParserOptions::newCanonical( 'canonical' );
354 $rr = new RenderedRevision(
355 $title,
356 $rev,
357 $options,
358 $this->combinerCallback,
359 RevisionRecord::RAW
360 );
361
362 $this->assertTrue( $rr->isContentDeleted(), 'isContentDeleted' );
363
364 $this->assertSame( $rev, $rr->getRevision() );
365 $this->assertSame( $options, $rr->getOptions() );
366
367 $html = $rr->getRevisionParserOutput()->getText();
368
369 // Suppressed content should be visible for sysops
370 $this->assertContains( 'page:RenderTestPage!', $html );
371 $this->assertContains( 'rev:11!', $html );
372 $this->assertContains( 'user:Frank!', $html );
373 $this->assertContains( 'time:20180101000003!', $html );
374
375 $this->assertSame( $html, $rr->getSlotParserOutput( SlotRecord::MAIN )->getText() );
376 }
377
379 $content = [
380 'main' => new WikitextContent( '[[Kittens]]' ),
381 'aux' => new WikitextContent( '[[Goats]]' ),
382 ];
383
384 $title = $this->getMockTitle( 7, 21 );
385 $rev = $this->getMockRevision( RevisionStoreRecord::class, $title, 11, 0, $content );
386
387 $options = ParserOptions::newCanonical( 'canonical' );
388 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
389
390 $combinedOutput = $rr->getRevisionParserOutput();
391 $mainOutput = $rr->getSlotParserOutput( SlotRecord::MAIN );
392 $auxOutput = $rr->getSlotParserOutput( 'aux' );
393
394 $combinedHtml = $combinedOutput->getText();
395 $mainHtml = $mainOutput->getText();
396 $auxHtml = $auxOutput->getText();
397
398 $this->assertContains( 'Kittens', $mainHtml );
399 $this->assertContains( 'Goats', $auxHtml );
400 $this->assertNotContains( 'Goats', $mainHtml );
401 $this->assertNotContains( 'Kittens', $auxHtml );
402 $this->assertContains( 'Kittens', $combinedHtml );
403 $this->assertContains( 'Goats', $combinedHtml );
404 $this->assertContains( 'aux', $combinedHtml, 'slot section header' );
405
406 $combinedLinks = $combinedOutput->getLinks();
407 $mainLinks = $mainOutput->getLinks();
408 $auxLinks = $auxOutput->getLinks();
409 $this->assertTrue( isset( $combinedLinks[NS_MAIN]['Kittens'] ), 'links from main slot' );
410 $this->assertTrue( isset( $combinedLinks[NS_MAIN]['Goats'] ), 'links from aux slot' );
411 $this->assertFalse( isset( $mainLinks[NS_MAIN]['Goats'] ), 'no aux links in main' );
412 $this->assertFalse( isset( $auxLinks[NS_MAIN]['Kittens'] ), 'no main links in aux' );
413 }
414
416 $title = $this->getMockTitle( 7, 21 );
417
418 $rev = new MutableRevisionRecord( $title );
419
420 $text = "";
421 $text .= "* page:{{PAGENAME}}!\n";
422 $text .= "* rev:{{REVISIONID}}!\n";
423 $text .= "* user:{{REVISIONUSER}}!\n";
424 $text .= "* time:{{REVISIONTIMESTAMP}}!\n";
425
426 $rev->setContent( SlotRecord::MAIN, new WikitextContent( $text ) );
427
428 $options = ParserOptions::newCanonical( 'canonical' );
429 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
430
431 // MutableRevisionRecord without ID should be used by the parser.
432 // USeful for fake
433 $html = $rr->getRevisionParserOutput()->getText();
434
435 $this->assertContains( 'page:RenderTestPage!', $html );
436 $this->assertContains( 'rev:!', $html );
437 $this->assertContains( 'user:!', $html );
438 $this->assertContains( 'time:!', $html );
439 }
440
442 $title = $this->getMockTitle( 7, 21 );
443
444 $rev = new MutableRevisionRecord( $title );
445 $rev->setId( 21 );
446
447 $text = "";
448 $text .= "* page:{{PAGENAME}}!\n";
449 $text .= "* rev:{{REVISIONID}}!\n";
450 $text .= "* user:{{REVISIONUSER}}!\n";
451 $text .= "* time:{{REVISIONTIMESTAMP}}!\n";
452
453 $rev->setContent( SlotRecord::MAIN, new WikitextContent( $text ) );
454
455 $actualRevision = $this->getMockRevision(
456 RevisionStoreRecord::class,
457 $title,
458 21,
459 RevisionRecord::DELETED_TEXT
460 );
461
462 $options = ParserOptions::newCanonical( 'canonical' );
463 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
464
465 // MutableRevisionRecord with ID should not be used by the parser,
466 // revision should be loaded instead!
467 $revisionStore = $this->getMockBuilder( RevisionStore::class )
468 ->disableOriginalConstructor()
469 ->getMock();
470
471 $revisionStore->expects( $this->once() )
472 ->method( 'getKnownCurrentRevision' )
473 ->with( $title, 0 )
474 ->willReturn( $actualRevision );
475
476 $this->setService( 'RevisionStore', $revisionStore );
477
478 $html = $rr->getRevisionParserOutput()->getText();
479
480 $this->assertContains( 'page:RenderTestPage!', $html );
481 $this->assertContains( 'rev:21!', $html );
482 $this->assertContains( 'user:Frank!', $html );
483 $this->assertContains( 'time:20180101000003!', $html );
484 }
485
486 public function testNoHtml() {
488 $mockContent = $this->getMockBuilder( WikitextContent::class )
489 ->setMethods( [ 'getParserOutput' ] )
490 ->setConstructorArgs( [ 'Whatever' ] )
491 ->getMock();
492 $mockContent->method( 'getParserOutput' )
493 ->willReturnCallback( function ( Title $title, $revId = null,
494 ParserOptions $options = null, $generateHtml = true
495 ) {
496 if ( !$generateHtml ) {
497 return new ParserOutput( null );
498 } else {
499 $this->fail( 'Should not be called with $generateHtml == true' );
500 return null; // never happens, make analyzer happy
501 }
502 } );
503
504 $title = $this->getMockTitle( 7, 21 );
505
506 $rev = new MutableRevisionRecord( $title );
507 $rev->setContent( SlotRecord::MAIN, $mockContent );
508 $rev->setContent( 'aux', $mockContent );
509
510 $options = ParserOptions::newCanonical( 'canonical' );
511 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
512
513 $output = $rr->getSlotParserOutput( SlotRecord::MAIN, [ 'generate-html' => false ] );
514 $this->assertFalse( $output->hasText(), 'hasText' );
515
516 $output = $rr->getRevisionParserOutput( [ 'generate-html' => false ] );
517 $this->assertFalse( $output->hasText(), 'hasText' );
518 }
519
520 public function testUpdateRevision() {
521 $title = $this->getMockTitle( 7, 21 );
522
523 $rev = new MutableRevisionRecord( $title );
524
525 $text = "";
526 $text .= "* page:{{PAGENAME}}!\n";
527 $text .= "* rev:{{REVISIONID}}!\n";
528 $text .= "* user:{{REVISIONUSER}}!\n";
529 $text .= "* time:{{REVISIONTIMESTAMP}}!\n";
530
531 $rev->setContent( SlotRecord::MAIN, new WikitextContent( $text ) );
532 $rev->setContent( 'aux', new WikitextContent( '[[Goats]]' ) );
533
534 $options = ParserOptions::newCanonical( 'canonical' );
535 $rr = new RenderedRevision( $title, $rev, $options, $this->combinerCallback );
536
537 $firstOutput = $rr->getRevisionParserOutput();
538 $mainOutput = $rr->getSlotParserOutput( SlotRecord::MAIN );
539 $auxOutput = $rr->getSlotParserOutput( 'aux' );
540
541 // emulate a saved revision
542 $savedRev = new MutableRevisionRecord( $title );
543 $savedRev->setContent( SlotRecord::MAIN, new WikitextContent( $text ) );
544 $savedRev->setContent( 'aux', new WikitextContent( '[[Goats]]' ) );
545 $savedRev->setId( 23 ); // saved, new
546 $savedRev->setUser( new UserIdentityValue( 9, 'Frank', 0 ) );
547 $savedRev->setTimestamp( '20180101000003' );
548
549 $rr->updateRevision( $savedRev );
550
551 $this->assertNotSame( $mainOutput, $rr->getSlotParserOutput( SlotRecord::MAIN ), 'Reset main' );
552 $this->assertSame( $auxOutput, $rr->getSlotParserOutput( 'aux' ), 'Keep aux' );
553
554 $updatedOutput = $rr->getRevisionParserOutput();
555 $html = $updatedOutput->getText();
556
557 $this->assertNotSame( $firstOutput, $updatedOutput, 'Reset merged' );
558 $this->assertContains( 'page:RenderTestPage!', $html );
559 $this->assertContains( 'rev:23!', $html );
560 $this->assertContains( 'user:Frank!', $html );
561 $this->assertContains( 'time:20180101000003!', $html );
562 $this->assertContains( 'Goats', $html );
563
564 $rr->updateRevision( $savedRev ); // should do nothing
565 $this->assertSame( $updatedOutput, $rr->getRevisionParserOutput(), 'no more reset needed' );
566 }
567
568}
they could even be mouse clicks or menu items whatever suits your program You should also get your if any
Definition COPYING.txt:326
Internationalisation code.
Definition Language.php:35
setService( $name, $object)
Sets a service, maintaining a stashed version of the previous service to be restored in tearDown.
static getTestUser( $groups=[])
Convenience method for getting an immutable test user.
Mutable RevisionRecord implementation, for building new revision entries programmatically.
Mutable version of RevisionSlots, for constructing a new revision.
RenderedRevision represents the rendered representation of a revision.
A RevisionRecord representing a revision of a deleted page persisted in the archive table.
Page revision base class.
A RevisionRecord representing an existing revision persisted in the revision table.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
Exception raised in response to an audience check when attempting to access suppressed information wi...
getMockRevision( $class, $title, $id=null, $visibility=0, array $content=null)
combineOutput(RenderedRevision $rrev, array $hints=[])
Value object representing a user's identity.
Set options of the Parser.
Represents a title within MediaWiki.
Definition Title.php:39
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:47
Content object for wiki text pages.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:2050
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:894
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition hooks.txt:2062
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place $output
Definition hooks.txt:2317
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1818
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
const NS_MAIN
Definition Defines.php:64
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:235
Base interface for content objects.
Definition Content.php:34
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$content