use CommentStoreComment;
use Content;
+use ContentHandler;
use LinksUpdate;
use MediaWiki\MediaWikiServices;
use MediaWiki\Storage\DerivedPageDataUpdater;
use MediaWiki\Storage\RevisionSlotsUpdate;
use MediaWiki\Storage\SlotRecord;
use MediaWikiTestCase;
+use MWCallableUpdate;
+use PHPUnit\Framework\MockObject\MockObject;
+use TextContent;
+use TextContentHandler;
use Title;
use User;
use Wikimedia\TestingAccessWrapper;
/**
* @group Database
*
- * @covers MediaWiki\Storage\DerivedPageDataUpdater
+ * @covers \MediaWiki\Storage\DerivedPageDataUpdater
*/
class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
$sysop = $this->getTestUser( [ 'sysop' ] )->getUser();
$page = $this->getPage( __METHOD__ );
- $mainContent1 = new WikitextContent( 'first [[main]] ({{REVISIONUSER}}) ~~~' );
- $mainContent2 = new WikitextContent( 'second' );
+ $mainContent1 = new WikitextContent( 'first [[main]] ({{REVISIONUSER}}) #~~~#' );
+ $mainContent2 = new WikitextContent( 'second ({{subst:REVISIONUSER}}) #~~~#' );
$rev = $this->createRevision( $page, 'first', $mainContent1 );
$mainContent1 = $rev->getContent( 'main' ); // get post-pst content
+ $userName = $rev->getUser()->getName();
+ $sysopName = $sysop->getName();
$update = new RevisionSlotsUpdate();
$update->modifyContent( 'main', $mainContent1 );
// parser-output for null-edit uses the original author's name
$html = $updater1->getRenderedRevision()->getRevisionParserOutput()->getText();
- $this->assertNotContains( $sysop->getName(), $html, '{{REVISIONUSER}}' );
+ $this->assertNotContains( $sysopName, $html, '{{REVISIONUSER}}' );
$this->assertNotContains( '{{REVISIONUSER}}', $html, '{{REVISIONUSER}}' );
- $this->assertContains( '(' . $rev->getUser()->getName() . ')', $html, '{{REVISIONUSER}}' );
+ $this->assertNotContains( '~~~', $html, 'signature ~~~' );
+ $this->assertContains( '(' . $userName . ')', $html, '{{REVISIONUSER}}' );
+ $this->assertContains( '>' . $userName . '<', $html, 'signature ~~~' );
// TODO: MCR: test inheritance from parent
$update = new RevisionSlotsUpdate();
$updater2 = $this->getDerivedPageDataUpdater( $page );
$updater2->prepareContent( $sysop, $update, false );
+ // non-null edit use the new user name in PST
+ $pstText = $updater2->getSlots()->getContent( 'main' )->serialize();
+ $this->assertNotContains( '{{subst:REVISIONUSER}}', $pstText, '{{subst:REVISIONUSER}}' );
+ $this->assertNotContains( '~~~', $pstText, 'signature ~~~' );
+ $this->assertContains( '(' . $sysopName . ')', $pstText, '{{subst:REVISIONUSER}}' );
+ $this->assertContains( ':' . $sysopName . '|', $pstText, 'signature ~~~' );
+
$this->assertFalse( $updater2->isCreation() );
$this->assertTrue( $updater2->isChange() );
}
$dataUpdates = $updater->getSecondaryDataUpdates();
- // TODO: MCR: assert updates from all slots!
$this->assertNotEmpty( $dataUpdates );
$linksUpdates = array_filter( $dataUpdates, function ( $du ) {
$this->assertCount( 1, $linksUpdates );
}
+ /**
+ * @param string $name
+ *
+ * @return ContentHandler
+ */
+ private function defineMockContentModelForUpdateTesting( $name ) {
+ /** @var ContentHandler|MockObject $handler */
+ $handler = $this->getMockBuilder( TextContentHandler::class )
+ ->setConstructorArgs( [ $name ] )
+ ->setMethods(
+ [ 'getSecondaryDataUpdates', 'getDeletionUpdates', 'unserializeContent' ]
+ )
+ ->getMock();
+
+ $dataUpdate = new MWCallableUpdate( 'time' );
+ $dataUpdate->_name = "$name data update";
+
+ $deletionUpdate = new MWCallableUpdate( 'time' );
+ $deletionUpdate->_name = "$name deletion update";
+
+ $handler->method( 'getSecondaryDataUpdates' )->willReturn( [ $dataUpdate ] );
+ $handler->method( 'getDeletionUpdates' )->willReturn( [ $deletionUpdate ] );
+ $handler->method( 'unserializeContent' )->willReturnCallback(
+ function ( $text ) use ( $handler ) {
+ return $this->createMockContent( $handler, $text );
+ }
+ );
+
+ $this->mergeMwGlobalArrayValue(
+ 'wgContentHandlers', [
+ $name => function () use ( $handler ){
+ return $handler;
+ }
+ ]
+ );
+
+ return $handler;
+ }
+
+ /**
+ * @param ContentHandler $handler
+ * @param string $text
+ *
+ * @return Content
+ */
+ private function createMockContent( ContentHandler $handler, $text ) {
+ /** @var Content|MockObject $content */
+ $content = $this->getMockBuilder( TextContent::class )
+ ->setConstructorArgs( [ $text ] )
+ ->setMethods( [ 'getModel', 'getContentHandler' ] )
+ ->getMock();
+
+ $content->method( 'getModel' )->willReturn( $handler->getModelID() );
+ $content->method( 'getContentHandler' )->willReturn( $handler );
+
+ return $content;
+ }
+
+ public function testGetSecondaryDataUpdatesWithSlotRemoval() {
+ global $wgMultiContentRevisionSchemaMigrationStage;
+
+ if ( ! ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ) {
+ $this->markTestSkipped( 'Slot removal cannot happen with MCR being enabled' );
+ }
+
+ $m1 = $this->defineMockContentModelForUpdateTesting( 'M1' );
+ $a1 = $this->defineMockContentModelForUpdateTesting( 'A1' );
+ $m2 = $this->defineMockContentModelForUpdateTesting( 'M2' );
+
+ $mainContent1 = $this->createMockContent( $m1, 'main 1' );
+ $auxContent1 = $this->createMockContent( $a1, 'aux 1' );
+ $mainContent2 = $this->createMockContent( $m2, 'main 2' );
+
+ $user = $this->getTestUser()->getUser();
+ $page = $this->getPage( __METHOD__ );
+ $this->createRevision(
+ $page,
+ __METHOD__,
+ [ 'main' => $mainContent1, 'aux' => $auxContent1 ]
+ );
+
+ $update = new RevisionSlotsUpdate();
+ $update->modifyContent( 'main', $mainContent2 );
+ $update->removeSlot( 'aux' );
+
+ $page = $this->getPage( __METHOD__ );
+ $updater = $this->getDerivedPageDataUpdater( $page );
+ $updater->prepareContent( $user, $update, false );
+
+ $dataUpdates = $updater->getSecondaryDataUpdates();
+
+ $this->assertNotEmpty( $dataUpdates );
+
+ $updateNames = array_map( function ( $du ) {
+ return isset( $du->_name ) ? $du->_name : get_class( $du );
+ }, $dataUpdates );
+
+ $this->assertContains( LinksUpdate::class, $updateNames );
+ $this->assertContains( 'A1 deletion update', $updateNames );
+ $this->assertContains( 'M2 data update', $updateNames );
+ $this->assertNotContains( 'M1 data update', $updateNames );
+ }
+
/**
* Creates a dummy revision object without touching the database.
*
return $rev;
}
+ /**
+ * @param int $id
+ * @return Title
+ */
+ private function getMockTitle( $id = 23 ) {
+ $mock = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mock->expects( $this->any() )
+ ->method( 'getDBkey' )
+ ->will( $this->returnValue( __CLASS__ ) );
+ $mock->expects( $this->any() )
+ ->method( 'getArticleID' )
+ ->will( $this->returnValue( $id ) );
+
+ return $mock;
+ }
+
public function provideIsReusableFor() {
- $title = Title::makeTitleSafe( NS_MAIN, __METHOD__ );
+ $title = $this->getMockTitle();
$user1 = User::newFromName( 'Alice' );
$user2 = User::newFromName( 'Bob' );
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
+ * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doSecondaryDataUpdates()
+ * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
*/
public function testDoUpdates() {
$page = $this->getPage( __METHOD__ );