Introduce two new classes, containing code split off from EditPage: * IntroMessageBuilder (edit notices and other intro messages) * PreloadedContentBuilder (initial text of new pages / sections) I'm doing both of these features in one change, because they share a lot of code. They are meant to be used by alternative editors to support all of the features of the MediaWiki edit form. This isn't everything you need yet (we should at least do this for the edit checkboxes too), but it's a step. Bug: T201613 Change-Id: If0b05710cb52a977bf4e85947d72d68683a0a29e
203 lines
7 KiB
PHP
203 lines
7 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Tests\EditPage;
|
|
|
|
use File;
|
|
use FileRepo;
|
|
use HashConfig;
|
|
use MediaWiki\EditPage\IntroMessageBuilder;
|
|
use MediaWiki\MainConfigNames;
|
|
use MediaWiki\Page\ProperPageIdentity;
|
|
use MediaWiki\Permissions\UltimateAuthority;
|
|
use MediaWiki\Title\Title;
|
|
use MediaWiki\User\TempUser\TempUserCreator;
|
|
use MediaWiki\User\UserFactory;
|
|
use MediaWiki\User\UserIdentityValue;
|
|
use MediaWikiIntegrationTestCase;
|
|
use MockMessageLocalizer;
|
|
use RepoGroup;
|
|
use User;
|
|
|
|
/**
|
|
* @covers \MediaWiki\EditPage\IntroMessageBuilder
|
|
* @group Database
|
|
*/
|
|
class IntroMessageBuilderTest extends MediaWikiIntegrationTestCase {
|
|
|
|
protected function setUp(): void {
|
|
$services = $this->getServiceContainer();
|
|
|
|
$config = new HashConfig( [
|
|
MainConfigNames::AllowUserCss => true,
|
|
MainConfigNames::AllowUserJs => true,
|
|
] );
|
|
|
|
$repoGroup = $this->createMock( RepoGroup::class );
|
|
$repoGroup
|
|
->method( 'findFile' )
|
|
->willReturnCallback( function ( ProperPageIdentity $title ) {
|
|
if ( $title->getDBkey() === 'Shared.png' || $title->getDBkey() === 'Shared-with-desc.png' ) {
|
|
$file = $this->createMock( File::class );
|
|
$file->method( 'isLocal' )->willReturn( false );
|
|
$file->method( 'getDescriptionUrl' )->willReturn( 'https://example.com/' );
|
|
$repo = $this->createMock( FileRepo::class );
|
|
$repo->method( 'getDisplayName' )->willReturn( '' );
|
|
$file->method( 'getRepo' )->willReturn( $repo );
|
|
return $file;
|
|
}
|
|
return null;
|
|
} );
|
|
|
|
$tempUserCreator = $this->createMock( TempUserCreator::class );
|
|
$tempUserCreator
|
|
->method( 'isAutoCreateAction' )
|
|
->willReturn( false );
|
|
|
|
$userFactory = $this->createMock( UserFactory::class );
|
|
$userFactory
|
|
->method( 'newFromName' )
|
|
->willReturnCallback( static function ( $name ) {
|
|
$user = new User;
|
|
$user->load(); // from 'defaults'
|
|
$user->mName = $name;
|
|
$user->mId = in_array( $name, [ 'Alice', 'Bob' ] ) ? 1 : 0;
|
|
$user->mDataLoaded = true;
|
|
return $user;
|
|
} );
|
|
|
|
$this->introMessageBuilder = new IntroMessageBuilder(
|
|
$config,
|
|
$services->getLinkRenderer(),
|
|
$services->getPermissionManager(),
|
|
$services->getUserNameUtils(),
|
|
$tempUserCreator,
|
|
$userFactory,
|
|
$services->getRestrictionStore(),
|
|
$services->getReadOnlyMode(),
|
|
$services->getSpecialPageFactory(),
|
|
$repoGroup,
|
|
$services->getNamespaceInfo(),
|
|
$services->getSkinFactory()
|
|
);
|
|
}
|
|
|
|
public static function provideCases() {
|
|
// title, oldid, user, editIntro, pages, expectedMessage, expectedWrap
|
|
|
|
yield 'Main namespace has no default message' =>
|
|
[ 'Hello', null, 'Alice', null, [ 'Hello' => '' ],
|
|
[], null ];
|
|
|
|
yield 'Logged-out warning' =>
|
|
[ 'Hello', null, null, null, [ 'Hello' => '' ],
|
|
[ "anoneditwarning" ], "mw-message-box-warning" ];
|
|
|
|
// Code and message editing
|
|
yield 'User JavaScript requires alert as well as code-specific message' =>
|
|
[ 'User:Bob/common.js', null, 'Alice', null, [ 'User:Bob/common.js' => '' ],
|
|
[ "userjsdangerous", "editpage-code-message" ], "mw-message-box-error" ];
|
|
|
|
yield 'Inform users that their JS is public and suggest guidelines' =>
|
|
[ 'User:Bob/common.js', null, 'Bob', null, [ 'User:Bob/common.js' => '' ],
|
|
[ "userjsispublic", "userjsdangerous", "editpage-code-message", "userjsyoucanpreview" ], "mw-message-box-error" ];
|
|
|
|
yield 'MediaWiki: namespace JSON requires alert' =>
|
|
[ 'MediaWiki:Map.json', null, 'Alice', null, [],
|
|
[ "editinginterface", "newarticletext" ], "mw-message-box-error" ];
|
|
|
|
yield 'MediaWiki: namespace message requires alert' =>
|
|
[ 'MediaWiki:Does-not-exist-asdfasdf', null, 'Alice', null, [],
|
|
[ "editinginterface", "newarticletext" ], "mw-message-box-error" ];
|
|
|
|
yield 'Translateable MediaWiki: namespace message links to Translatewiki' =>
|
|
[ 'MediaWiki:View', null, 'Alice', null, [],
|
|
[ "editinginterface", "translateinterface", "newarticletext" ], "mw-message-box-error" ];
|
|
|
|
// Files
|
|
yield 'Neither shared not local file exists' =>
|
|
[ 'File:Missing.png', null, 'Alice', null, [],
|
|
[ "newarticletext" ], "mw-newarticletext" ];
|
|
|
|
yield 'Shared file exists, local description does not exist' =>
|
|
[ 'File:Shared.png', null, 'Alice', null, [],
|
|
[ "sharedupload-desc-create", "newarticletext" ], "mw-sharedupload-desc-create" ];
|
|
|
|
yield 'Shared file exists, local description exists' =>
|
|
[ 'File:Shared-with-desc.png', null, 'Alice', null, [ 'File:Shared-with-desc.png' => 'Test' ],
|
|
[ "sharedupload-desc-edit" ], "mw-sharedupload-desc-edit" ];
|
|
|
|
// Users
|
|
yield 'User does not exist' =>
|
|
[ 'User:Foo', null, 'Alice', null, [],
|
|
[ "userpage-userdoesnotexist", "newarticletext" ], "mw-newarticletext" ];
|
|
|
|
yield 'User exists' =>
|
|
[ 'User:Bob', null, 'Alice', null, [ 'User:Bob' => '' ],
|
|
[], null ];
|
|
|
|
yield 'IP user exists, I guess' =>
|
|
[ 'User:1.2.3.4', null, 'Alice', null, [ 'User:1.2.3.4' => '' ],
|
|
[], null ];
|
|
|
|
// Editintro
|
|
yield 'Default edit intro for missing page' =>
|
|
[ 'Does-not-exist-asdfasdf', null, 'Alice', null, [],
|
|
[ "newarticletext" ], "mw-newarticletext" ];
|
|
|
|
yield 'The "editintro" parameter replaces the default edit intro' =>
|
|
[ 'Does-not-exist-asdfasdf', null, 'Alice', 'Template:Editintro', [ 'Template:Editintro' => '(editintro)' ],
|
|
[ "editintro" ], null ];
|
|
|
|
// So many more cases to add...
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideCases
|
|
*/
|
|
public function testGetIntroMessages( $title, $oldid, $user, $editIntro, $pages, $expectedMessages, $expectedWrap ) {
|
|
foreach ( $pages as $page => $content ) {
|
|
$this->editPage( $page, $content );
|
|
}
|
|
|
|
if ( $user ) {
|
|
$userObj = UserIdentityValue::newRegistered( 1, $user );
|
|
} else {
|
|
$userObj = UserIdentityValue::newAnonymous( '1.2.3.4' );
|
|
}
|
|
|
|
$parameters = [
|
|
// These messages are always included, skip them to simplify expected values
|
|
[ 'editnotice-notext', 'editpage-head-copy-warn' ],
|
|
new MockMessageLocalizer( 'qqx' ),
|
|
Title::newFromText( $title )->toPageIdentity(),
|
|
null,
|
|
new UltimateAuthority( $userObj ),
|
|
$editIntro,
|
|
null,
|
|
false
|
|
];
|
|
|
|
$result = $this->introMessageBuilder->getIntroMessages( IntroMessageBuilder::LESS_FRAMES, ...$parameters );
|
|
$resultLessFrames = implode( '', $result );
|
|
$result = $this->introMessageBuilder->getIntroMessages( IntroMessageBuilder::MORE_FRAMES, ...$parameters );
|
|
$resultMoreFrames = implode( '', $result );
|
|
|
|
// Find anything that looks like a message in the output
|
|
preg_match_all( '/[(](.+?)[):]/', $resultLessFrames, $matches, PREG_PATTERN_ORDER );
|
|
$this->assertEquals( $expectedMessages, $matches[1], 'Messages (less frames)' );
|
|
if ( $expectedWrap !== null ) {
|
|
$this->assertStringNotContainsString( $expectedWrap, $resultLessFrames, 'No frames (less frames)' );
|
|
}
|
|
|
|
preg_match_all( '/[(](.+?)[):]/', $resultMoreFrames, $matches, PREG_PATTERN_ORDER );
|
|
$this->assertEquals( $expectedMessages, $matches[1], 'Messages (more frames)' );
|
|
if ( $expectedWrap !== null ) {
|
|
$this->assertStringContainsString( $expectedWrap, $resultMoreFrames, 'Frames (more frames)' );
|
|
}
|
|
|
|
if ( $expectedWrap == null ) {
|
|
$this->assertEquals( $resultLessFrames, $resultMoreFrames, 'No frames' );
|
|
}
|
|
}
|
|
|
|
}
|