summaryrefslogtreecommitdiff
path: root/www/wiki/tests/phpunit/includes/content
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/tests/phpunit/includes/content')
-rw-r--r--www/wiki/tests/phpunit/includes/content/ContentHandlerTest.php497
-rw-r--r--www/wiki/tests/phpunit/includes/content/CssContentHandlerTest.php41
-rw-r--r--www/wiki/tests/phpunit/includes/content/CssContentTest.php133
-rw-r--r--www/wiki/tests/phpunit/includes/content/FileContentHandlerTest.php52
-rw-r--r--www/wiki/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php41
-rw-r--r--www/wiki/tests/phpunit/includes/content/JavaScriptContentTest.php327
-rw-r--r--www/wiki/tests/phpunit/includes/content/JsonContentHandlerTest.php14
-rw-r--r--www/wiki/tests/phpunit/includes/content/JsonContentTest.php152
-rw-r--r--www/wiki/tests/phpunit/includes/content/TextContentHandlerTest.php55
-rw-r--r--www/wiki/tests/phpunit/includes/content/TextContentTest.php477
-rw-r--r--www/wiki/tests/phpunit/includes/content/WikitextContentHandlerTest.php365
-rw-r--r--www/wiki/tests/phpunit/includes/content/WikitextContentTest.php443
-rw-r--r--www/wiki/tests/phpunit/includes/content/WikitextStructureTest.php110
13 files changed, 2707 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/content/ContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/ContentHandlerTest.php
new file mode 100644
index 00000000..309b7b11
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/ContentHandlerTest.php
@@ -0,0 +1,497 @@
+<?php
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @group ContentHandler
+ * @group Database
+ */
+class ContentHandlerTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ global $wgContLang;
+ parent::setUp();
+
+ $this->setMwGlobals( [
+ 'wgExtraNamespaces' => [
+ 12312 => 'Dummy',
+ 12313 => 'Dummy_talk',
+ ],
+ // The below tests assume that namespaces not mentioned here (Help, User, MediaWiki, ..)
+ // default to CONTENT_MODEL_WIKITEXT.
+ 'wgNamespaceContentModels' => [
+ 12312 => 'testing',
+ ],
+ 'wgContentHandlers' => [
+ CONTENT_MODEL_WIKITEXT => WikitextContentHandler::class,
+ CONTENT_MODEL_JAVASCRIPT => JavaScriptContentHandler::class,
+ CONTENT_MODEL_JSON => JsonContentHandler::class,
+ CONTENT_MODEL_CSS => CssContentHandler::class,
+ CONTENT_MODEL_TEXT => TextContentHandler::class,
+ 'testing' => DummyContentHandlerForTesting::class,
+ 'testing-callbacks' => function ( $modelId ) {
+ return new DummyContentHandlerForTesting( $modelId );
+ }
+ ],
+ ] );
+
+ // Reset namespace cache
+ MWNamespace::clearCaches();
+ $wgContLang->resetNamespaces();
+ // And LinkCache
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'LinkCache' );
+ }
+
+ protected function tearDown() {
+ global $wgContLang;
+
+ // Reset namespace cache
+ MWNamespace::clearCaches();
+ $wgContLang->resetNamespaces();
+ // And LinkCache
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'LinkCache' );
+
+ parent::tearDown();
+ }
+
+ public function addDBDataOnce() {
+ $this->insertPage( 'Not_Main_Page', 'This is not a main page' );
+ $this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]' );
+ }
+
+ public static function dataGetDefaultModelFor() {
+ return [
+ [ 'Help:Foo', CONTENT_MODEL_WIKITEXT ],
+ [ 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ],
+ [ 'Help:Foo.css', CONTENT_MODEL_WIKITEXT ],
+ [ 'Help:Foo.json', CONTENT_MODEL_WIKITEXT ],
+ [ 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ],
+ [ 'User:Foo', CONTENT_MODEL_WIKITEXT ],
+ [ 'User:Foo.js', CONTENT_MODEL_WIKITEXT ],
+ [ 'User:Foo.css', CONTENT_MODEL_WIKITEXT ],
+ [ 'User:Foo.json', CONTENT_MODEL_WIKITEXT ],
+ [ 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ],
+ [ 'User:Foo/bar.css', CONTENT_MODEL_CSS ],
+ [ 'User:Foo/bar.json', CONTENT_MODEL_JSON ],
+ [ 'User:Foo/bar.json.nope', CONTENT_MODEL_WIKITEXT ],
+ [ 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ],
+ [ 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ],
+ [ 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ],
+ [ 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ],
+ [ 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ],
+ [ 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ],
+ [ 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ],
+ [ 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ],
+ [ 'MediaWiki:Foo.json', CONTENT_MODEL_JSON ],
+ [ 'MediaWiki:Foo.JSON', CONTENT_MODEL_WIKITEXT ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetDefaultModelFor
+ * @covers ContentHandler::getDefaultModelFor
+ */
+ public function testGetDefaultModelFor( $title, $expectedModelId ) {
+ $title = Title::newFromText( $title );
+ $this->assertEquals( $expectedModelId, ContentHandler::getDefaultModelFor( $title ) );
+ }
+
+ /**
+ * @dataProvider dataGetDefaultModelFor
+ * @covers ContentHandler::getForTitle
+ */
+ public function testGetForTitle( $title, $expectedContentModel ) {
+ $title = Title::newFromText( $title );
+ LinkCache::singleton()->addBadLinkObj( $title );
+ $handler = ContentHandler::getForTitle( $title );
+ $this->assertEquals( $expectedContentModel, $handler->getModelID() );
+ }
+
+ public static function dataGetLocalizedName() {
+ return [
+ [ null, null ],
+ [ "xyzzy", null ],
+
+ // XXX: depends on content language
+ [ CONTENT_MODEL_JAVASCRIPT, '/javascript/i' ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetLocalizedName
+ * @covers ContentHandler::getLocalizedName
+ */
+ public function testGetLocalizedName( $id, $expected ) {
+ $name = ContentHandler::getLocalizedName( $id );
+
+ if ( $expected ) {
+ $this->assertNotNull( $name, "no name found for content model $id" );
+ $this->assertTrue( preg_match( $expected, $name ) > 0,
+ "content model name for #$id did not match pattern $expected"
+ );
+ } else {
+ $this->assertEquals( $id, $name, "localization of unknown model $id should have "
+ . "fallen back to use the model id directly."
+ );
+ }
+ }
+
+ public static function dataGetPageLanguage() {
+ global $wgLanguageCode;
+
+ return [
+ [ "Main", $wgLanguageCode ],
+ [ "Dummy:Foo", $wgLanguageCode ],
+ [ "MediaWiki:common.js", 'en' ],
+ [ "User:Foo/common.js", 'en' ],
+ [ "MediaWiki:common.css", 'en' ],
+ [ "User:Foo/common.css", 'en' ],
+ [ "User:Foo", $wgLanguageCode ],
+
+ [ CONTENT_MODEL_JAVASCRIPT, 'javascript' ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetPageLanguage
+ * @covers ContentHandler::getPageLanguage
+ */
+ public function testGetPageLanguage( $title, $expected ) {
+ if ( is_string( $title ) ) {
+ $title = Title::newFromText( $title );
+ LinkCache::singleton()->addBadLinkObj( $title );
+ }
+
+ $expected = wfGetLangObj( $expected );
+
+ $handler = ContentHandler::getForTitle( $title );
+ $lang = $handler->getPageLanguage( $title );
+
+ $this->assertEquals( $expected->getCode(), $lang->getCode() );
+ }
+
+ public static function dataGetContentText_Null() {
+ return [
+ [ 'fail' ],
+ [ 'serialize' ],
+ [ 'ignore' ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetContentText_Null
+ * @covers ContentHandler::getContentText
+ */
+ public function testGetContentText_Null( $contentHandlerTextFallback ) {
+ $this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback );
+
+ $content = null;
+
+ $text = ContentHandler::getContentText( $content );
+ $this->assertEquals( '', $text );
+ }
+
+ public static function dataGetContentText_TextContent() {
+ return [
+ [ 'fail' ],
+ [ 'serialize' ],
+ [ 'ignore' ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetContentText_TextContent
+ * @covers ContentHandler::getContentText
+ */
+ public function testGetContentText_TextContent( $contentHandlerTextFallback ) {
+ $this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback );
+
+ $content = new WikitextContent( "hello world" );
+
+ $text = ContentHandler::getContentText( $content );
+ $this->assertEquals( $content->getNativeData(), $text );
+ }
+
+ /**
+ * ContentHandler::getContentText should have thrown an exception for non-text Content object
+ * @expectedException MWException
+ * @covers ContentHandler::getContentText
+ */
+ public function testGetContentText_NonTextContent_fail() {
+ $this->setMwGlobals( 'wgContentHandlerTextFallback', 'fail' );
+
+ $content = new DummyContentForTesting( "hello world" );
+
+ ContentHandler::getContentText( $content );
+ }
+
+ /**
+ * @covers ContentHandler::getContentText
+ */
+ public function testGetContentText_NonTextContent_serialize() {
+ $this->setMwGlobals( 'wgContentHandlerTextFallback', 'serialize' );
+
+ $content = new DummyContentForTesting( "hello world" );
+
+ $text = ContentHandler::getContentText( $content );
+ $this->assertEquals( $content->serialize(), $text );
+ }
+
+ /**
+ * @covers ContentHandler::getContentText
+ */
+ public function testGetContentText_NonTextContent_ignore() {
+ $this->setMwGlobals( 'wgContentHandlerTextFallback', 'ignore' );
+
+ $content = new DummyContentForTesting( "hello world" );
+
+ $text = ContentHandler::getContentText( $content );
+ $this->assertNull( $text );
+ }
+
+ public static function dataMakeContent() {
+ return [
+ [ 'hallo', 'Help:Test', null, null, CONTENT_MODEL_WIKITEXT, 'hallo', false ],
+ [ 'hallo', 'MediaWiki:Test.js', null, null, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ],
+ [ serialize( 'hallo' ), 'Dummy:Test', null, null, "testing", 'hallo', false ],
+
+ [
+ 'hallo',
+ 'Help:Test',
+ null,
+ CONTENT_FORMAT_WIKITEXT,
+ CONTENT_MODEL_WIKITEXT,
+ 'hallo',
+ false
+ ],
+ [
+ 'hallo',
+ 'MediaWiki:Test.js',
+ null,
+ CONTENT_FORMAT_JAVASCRIPT,
+ CONTENT_MODEL_JAVASCRIPT,
+ 'hallo',
+ false
+ ],
+ [ serialize( 'hallo' ), 'Dummy:Test', null, "testing", "testing", 'hallo', false ],
+
+ [ 'hallo', 'Help:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ],
+ [
+ 'hallo',
+ 'MediaWiki:Test.js',
+ CONTENT_MODEL_CSS,
+ null,
+ CONTENT_MODEL_CSS,
+ 'hallo',
+ false
+ ],
+ [
+ serialize( 'hallo' ),
+ 'Dummy:Test',
+ CONTENT_MODEL_CSS,
+ null,
+ CONTENT_MODEL_CSS,
+ serialize( 'hallo' ),
+ false
+ ],
+
+ [ 'hallo', 'Help:Test', CONTENT_MODEL_WIKITEXT, "testing", null, null, true ],
+ [ 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, "testing", null, null, true ],
+ [ 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT, "testing", null, null, true ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataMakeContent
+ * @covers ContentHandler::makeContent
+ */
+ public function testMakeContent( $data, $title, $modelId, $format,
+ $expectedModelId, $expectedNativeData, $shouldFail
+ ) {
+ $title = Title::newFromText( $title );
+ LinkCache::singleton()->addBadLinkObj( $title );
+ try {
+ $content = ContentHandler::makeContent( $data, $title, $modelId, $format );
+
+ if ( $shouldFail ) {
+ $this->fail( "ContentHandler::makeContent should have failed!" );
+ }
+
+ $this->assertEquals( $expectedModelId, $content->getModel(), 'bad model id' );
+ $this->assertEquals( $expectedNativeData, $content->getNativeData(), 'bads native data' );
+ } catch ( MWException $ex ) {
+ if ( !$shouldFail ) {
+ $this->fail( "ContentHandler::makeContent failed unexpectedly: " . $ex->getMessage() );
+ } else {
+ // dummy, so we don't get the "test did not perform any assertions" message.
+ $this->assertTrue( true );
+ }
+ }
+ }
+
+ /**
+ * @covers ContentHandler::getAutosummary
+ *
+ * Test if we become a "Created blank page" summary from getAutoSummary if no Content added to
+ * page.
+ */
+ public function testGetAutosummary() {
+ $this->setMwGlobals( 'wgContLang', Language::factory( 'en' ) );
+
+ $content = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
+ $title = Title::newFromText( 'Help:Test' );
+ // Create a new content object with no content
+ $newContent = ContentHandler::makeContent( '', $title, CONTENT_MODEL_WIKITEXT, null );
+ // first check, if we become a blank page created summary with the right bitmask
+ $autoSummary = $content->getAutosummary( null, $newContent, 97 );
+ $this->assertEquals( $autoSummary,
+ wfMessage( 'autosumm-newblank' )->inContentLanguage()->text() );
+ // now check, what we become with another bitmask
+ $autoSummary = $content->getAutosummary( null, $newContent, 92 );
+ $this->assertEquals( $autoSummary, '' );
+ }
+
+ /**
+ * Test software tag that is added when content model of the page changes
+ * @covers ContentHandler::getChangeTag
+ */
+ public function testGetChangeTag() {
+ $this->setMwGlobals( 'wgSoftwareTags', [ 'mw-contentmodelchange' => true ] );
+ $wikitextContentHandler = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
+ // Create old content object with javascript content model
+ $oldContent = ContentHandler::makeContent( '', null, CONTENT_MODEL_JAVASCRIPT, null );
+ // Create new content object with wikitext content model
+ $newContent = ContentHandler::makeContent( '', null, CONTENT_MODEL_WIKITEXT, null );
+ // Get the tag for this edit
+ $tag = $wikitextContentHandler->getChangeTag( $oldContent, $newContent, EDIT_UPDATE );
+ $this->assertSame( $tag, 'mw-contentmodelchange' );
+ }
+
+ /**
+ * @covers ContentHandler::supportsCategories
+ */
+ public function testSupportsCategories() {
+ $handler = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
+ $this->assertTrue( $handler->supportsCategories(), 'content model supports categories' );
+ }
+
+ /**
+ * @covers ContentHandler::supportsDirectEditing
+ */
+ public function testSupportsDirectEditing() {
+ $handler = new DummyContentHandlerForTesting( CONTENT_MODEL_JSON );
+ $this->assertFalse( $handler->supportsDirectEditing(), 'direct editing is not supported' );
+ }
+
+ public static function dummyHookHandler( $foo, &$text, $bar ) {
+ if ( $text === null || $text === false ) {
+ return false;
+ }
+
+ $text = strtoupper( $text );
+
+ return true;
+ }
+
+ public function provideGetModelForID() {
+ return [
+ [ CONTENT_MODEL_WIKITEXT, WikitextContentHandler::class ],
+ [ CONTENT_MODEL_JAVASCRIPT, JavaScriptContentHandler::class ],
+ [ CONTENT_MODEL_JSON, JsonContentHandler::class ],
+ [ CONTENT_MODEL_CSS, CssContentHandler::class ],
+ [ CONTENT_MODEL_TEXT, TextContentHandler::class ],
+ [ 'testing', DummyContentHandlerForTesting::class ],
+ [ 'testing-callbacks', DummyContentHandlerForTesting::class ],
+ ];
+ }
+
+ /**
+ * @covers ContentHandler::getForModelID
+ * @dataProvider provideGetModelForID
+ */
+ public function testGetModelForID( $modelId, $handlerClass ) {
+ $handler = ContentHandler::getForModelID( $modelId );
+
+ $this->assertInstanceOf( $handlerClass, $handler );
+ }
+
+ /**
+ * @covers ContentHandler::getFieldsForSearchIndex
+ */
+ public function testGetFieldsForSearchIndex() {
+ $searchEngine = $this->newSearchEngine();
+
+ $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
+
+ $fields = $handler->getFieldsForSearchIndex( $searchEngine );
+
+ $this->assertArrayHasKey( 'category', $fields );
+ $this->assertArrayHasKey( 'external_link', $fields );
+ $this->assertArrayHasKey( 'outgoing_link', $fields );
+ $this->assertArrayHasKey( 'template', $fields );
+ $this->assertArrayHasKey( 'content_model', $fields );
+ }
+
+ private function newSearchEngine() {
+ $searchEngine = $this->getMockBuilder( SearchEngine::class )
+ ->getMock();
+
+ $searchEngine->expects( $this->any() )
+ ->method( 'makeSearchFieldMapping' )
+ ->will( $this->returnCallback( function ( $name, $type ) {
+ return new DummySearchIndexFieldDefinition( $name, $type );
+ } ) );
+
+ return $searchEngine;
+ }
+
+ /**
+ * @covers ContentHandler::getDataForSearchIndex
+ */
+ public function testDataIndexFields() {
+ $mockEngine = $this->createMock( SearchEngine::class );
+ $title = Title::newFromText( 'Not_Main_Page', NS_MAIN );
+ $page = new WikiPage( $title );
+
+ $this->setTemporaryHook( 'SearchDataForIndex',
+ function (
+ &$fields,
+ ContentHandler $handler,
+ WikiPage $page,
+ ParserOutput $output,
+ SearchEngine $engine
+ ) {
+ $fields['testDataField'] = 'test content';
+ } );
+
+ $output = $page->getContent()->getParserOutput( $title );
+ $data = $page->getContentHandler()->getDataForSearchIndex( $page, $output, $mockEngine );
+ $this->assertArrayHasKey( 'text', $data );
+ $this->assertArrayHasKey( 'text_bytes', $data );
+ $this->assertArrayHasKey( 'language', $data );
+ $this->assertArrayHasKey( 'testDataField', $data );
+ $this->assertEquals( 'test content', $data['testDataField'] );
+ $this->assertEquals( 'wikitext', $data['content_model'] );
+ }
+
+ /**
+ * @covers ContentHandler::getParserOutputForIndexing
+ */
+ public function testParserOutputForIndexing() {
+ $title = Title::newFromText( 'Smithee', NS_MAIN );
+ $page = new WikiPage( $title );
+
+ $out = $page->getContentHandler()->getParserOutputForIndexing( $page );
+ $this->assertInstanceOf( ParserOutput::class, $out );
+ $this->assertContains( 'one who smiths', $out->getRawText() );
+ }
+
+ /**
+ * @covers ContentHandler::getContentModels
+ */
+ public function testGetContentModelsHook() {
+ $this->setTemporaryHook( 'GetContentModels', function ( &$models ) {
+ $models[] = 'Ferrari';
+ } );
+ $this->assertContains( 'Ferrari', ContentHandler::getContentModels() );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/CssContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/CssContentHandlerTest.php
new file mode 100644
index 00000000..7ca1afce
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/CssContentHandlerTest.php
@@ -0,0 +1,41 @@
+<?php
+
+class CssContentHandlerTest extends MediaWikiLangTestCase {
+
+ /**
+ * @dataProvider provideMakeRedirectContent
+ * @covers CssContentHandler::makeRedirectContent
+ */
+ public function testMakeRedirectContent( $title, $expected ) {
+ $this->setMwGlobals( [
+ 'wgServer' => '//example.org',
+ 'wgScript' => '/w/index.php',
+ ] );
+ $ch = new CssContentHandler();
+ $content = $ch->makeRedirectContent( Title::newFromText( $title ) );
+ $this->assertInstanceOf( CssContent::class, $content );
+ $this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_CSS ) );
+ }
+
+ /**
+ * Keep this in sync with CssContentTest::provideGetRedirectTarget()
+ */
+ public static function provideMakeRedirectContent() {
+ // phpcs:disable Generic.Files.LineLength
+ return [
+ [
+ 'MediaWiki:MonoBook.css',
+ "/* #REDIRECT */@import url(//example.org/w/index.php?title=MediaWiki:MonoBook.css&action=raw&ctype=text/css);"
+ ],
+ [
+ 'User:FooBar/common.css',
+ "/* #REDIRECT */@import url(//example.org/w/index.php?title=User:FooBar/common.css&action=raw&ctype=text/css);"
+ ],
+ [
+ 'Gadget:FooBaz.css',
+ "/* #REDIRECT */@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);"
+ ],
+ ];
+ // phpcs:enable
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/CssContentTest.php b/www/wiki/tests/phpunit/includes/content/CssContentTest.php
new file mode 100644
index 00000000..f5cc05e0
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/CssContentTest.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @group ContentHandler
+ * @group Database
+ * ^--- needed, because we do need the database to test link updates
+ *
+ * @FIXME this should not extend JavaScriptContentTest.
+ */
+class CssContentTest extends JavaScriptContentTest {
+
+ protected function setUp() {
+ parent::setUp();
+
+ // Anon user
+ $user = new User();
+ $user->setName( '127.0.0.1' );
+
+ $this->setMwGlobals( [
+ 'wgUser' => $user,
+ 'wgTextModelsToParse' => [
+ CONTENT_MODEL_CSS,
+ ]
+ ] );
+ }
+
+ public function newContent( $text ) {
+ return new CssContent( $text );
+ }
+
+ public static function dataGetParserOutput() {
+ return [
+ [
+ 'MediaWiki:Test.css',
+ null,
+ "hello <world>\n",
+ "<pre class=\"mw-code mw-css\" dir=\"ltr\">\nhello &lt;world&gt;\n\n</pre>"
+ ],
+ [
+ 'MediaWiki:Test.css',
+ null,
+ "/* hello [[world]] */\n",
+ "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n/* hello [[world]] */\n\n</pre>",
+ [
+ 'Links' => [
+ [ 'World' => 0 ]
+ ]
+ ]
+ ],
+
+ // TODO: more...?
+ ];
+ }
+
+ /**
+ * @covers CssContent::getModel
+ */
+ public function testGetModel() {
+ $content = $this->newContent( 'hello world.' );
+
+ $this->assertEquals( CONTENT_MODEL_CSS, $content->getModel() );
+ }
+
+ /**
+ * @covers CssContent::getContentHandler
+ */
+ public function testGetContentHandler() {
+ $content = $this->newContent( 'hello world.' );
+
+ $this->assertEquals( CONTENT_MODEL_CSS, $content->getContentHandler()->getModelID() );
+ }
+
+ /**
+ * Redirects aren't supported
+ */
+ public static function provideUpdateRedirect() {
+ return [
+ [
+ '#REDIRECT [[Someplace]]',
+ '#REDIRECT [[Someplace]]',
+ ],
+ ];
+ }
+
+ /**
+ * @covers CssContent::getRedirectTarget
+ * @dataProvider provideGetRedirectTarget
+ */
+ public function testGetRedirectTarget( $title, $text ) {
+ $this->setMwGlobals( [
+ 'wgServer' => '//example.org',
+ 'wgScriptPath' => '/w',
+ 'wgScript' => '/w/index.php',
+ ] );
+ $content = new CssContent( $text );
+ $target = $content->getRedirectTarget();
+ $this->assertEquals( $title, $target ? $target->getPrefixedText() : null );
+ }
+
+ /**
+ * Keep this in sync with CssContentHandlerTest::provideMakeRedirectContent()
+ */
+ public static function provideGetRedirectTarget() {
+ // phpcs:disable Generic.Files.LineLength
+ return [
+ [ 'MediaWiki:MonoBook.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=MediaWiki:MonoBook.css&action=raw&ctype=text/css);" ],
+ [ 'User:FooBar/common.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=User:FooBar/common.css&action=raw&ctype=text/css);" ],
+ [ 'Gadget:FooBaz.css', "/* #REDIRECT */@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ],
+ # No #REDIRECT comment
+ [ null, "@import url(//example.org/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ],
+ # Wrong domain
+ [ null, "/* #REDIRECT */@import url(//example.com/w/index.php?title=Gadget:FooBaz.css&action=raw&ctype=text/css);" ],
+ ];
+ // phpcs:enable
+ }
+
+ public static function dataEquals() {
+ return [
+ [ new CssContent( 'hallo' ), null, false ],
+ [ new CssContent( 'hallo' ), new CssContent( 'hallo' ), true ],
+ [ new CssContent( 'hallo' ), new WikitextContent( 'hallo' ), false ],
+ [ new CssContent( 'hallo' ), new CssContent( 'HALLO' ), false ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataEquals
+ * @covers CssContent::equals
+ */
+ public function testEquals( Content $a, Content $b = null, $equal = false ) {
+ $this->assertEquals( $equal, $a->equals( $b ) );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/FileContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/FileContentHandlerTest.php
new file mode 100644
index 00000000..9149fc4f
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/FileContentHandlerTest.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @covers FileContentHandler
+ */
+class FileContentHandlerTest extends MediaWikiLangTestCase {
+ /**
+ * @var FileContentHandler
+ */
+ private $handler;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->handler = new FileContentHandler();
+ }
+
+ public function testIndexMapping() {
+ $mockEngine = $this->createMock( SearchEngine::class );
+
+ $mockEngine->expects( $this->atLeastOnce() )
+ ->method( 'makeSearchFieldMapping' )
+ ->willReturnCallback( function ( $name, $type ) {
+ $mockField =
+ $this->getMockBuilder( SearchIndexFieldDefinition::class )
+ ->setMethods( [ 'getMapping' ] )
+ ->setConstructorArgs( [ $name, $type ] )
+ ->getMock();
+ return $mockField;
+ } );
+
+ $map = $this->handler->getFieldsForSearchIndex( $mockEngine );
+ $expect = [
+ 'file_media_type' => 1,
+ 'file_mime' => 1,
+ 'file_size' => 1,
+ 'file_width' => 1,
+ 'file_height' => 1,
+ 'file_bits' => 1,
+ 'file_resolution' => 1,
+ 'file_text' => 1,
+ ];
+ foreach ( $map as $name => $field ) {
+ $this->assertInstanceOf( SearchIndexField::class, $field );
+ $this->assertEquals( $name, $field->getName() );
+ unset( $expect[$name] );
+ }
+ $this->assertEmpty( $expect );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php
new file mode 100644
index 00000000..b5e3ab4a
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/JavaScriptContentHandlerTest.php
@@ -0,0 +1,41 @@
+<?php
+
+class JavaScriptContentHandlerTest extends MediaWikiLangTestCase {
+
+ /**
+ * @dataProvider provideMakeRedirectContent
+ * @covers JavaScriptContentHandler::makeRedirectContent
+ */
+ public function testMakeRedirectContent( $title, $expected ) {
+ $this->setMwGlobals( [
+ 'wgServer' => '//example.org',
+ 'wgScript' => '/w/index.php',
+ ] );
+ $ch = new JavaScriptContentHandler();
+ $content = $ch->makeRedirectContent( Title::newFromText( $title ) );
+ $this->assertInstanceOf( JavaScriptContent::class, $content );
+ $this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_JAVASCRIPT ) );
+ }
+
+ /**
+ * Keep this in sync with JavaScriptContentTest::provideGetRedirectTarget()
+ */
+ public static function provideMakeRedirectContent() {
+ // phpcs:disable Generic.Files.LineLength
+ return [
+ [
+ 'MediaWiki:MonoBook.js',
+ '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");'
+ ],
+ [
+ 'User:FooBar/common.js',
+ '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=User:FooBar/common.js\u0026action=raw\u0026ctype=text/javascript");'
+ ],
+ [
+ 'Gadget:FooBaz.js',
+ '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");'
+ ],
+ ];
+ // phpcs:enable
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/JavaScriptContentTest.php b/www/wiki/tests/phpunit/includes/content/JavaScriptContentTest.php
new file mode 100644
index 00000000..823be6f7
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/JavaScriptContentTest.php
@@ -0,0 +1,327 @@
+<?php
+
+/**
+ * @group ContentHandler
+ * @group Database
+ * ^--- needed, because we do need the database to test link updates
+ */
+class JavaScriptContentTest extends TextContentTest {
+
+ public function newContent( $text ) {
+ return new JavaScriptContent( $text );
+ }
+
+ public static function dataGetParserOutput() {
+ return [
+ [
+ 'MediaWiki:Test.js',
+ null,
+ "hello <world>\n",
+ "<pre class=\"mw-code mw-js\" dir=\"ltr\">\nhello &lt;world&gt;\n\n</pre>"
+ ],
+ [
+ 'MediaWiki:Test.js',
+ null,
+ "hello(); // [[world]]\n",
+ "<pre class=\"mw-code mw-js\" dir=\"ltr\">\nhello(); // [[world]]\n\n</pre>",
+ [
+ 'Links' => [
+ [ 'World' => 0 ]
+ ]
+ ]
+ ],
+
+ // TODO: more...?
+ ];
+ }
+
+ // XXX: Unused function
+ public static function dataGetSection() {
+ return [
+ [ WikitextContentTest::$sections,
+ '0',
+ null
+ ],
+ [ WikitextContentTest::$sections,
+ '2',
+ null
+ ],
+ [ WikitextContentTest::$sections,
+ '8',
+ null
+ ],
+ ];
+ }
+
+ // XXX: Unused function
+ public static function dataReplaceSection() {
+ return [
+ [ WikitextContentTest::$sections,
+ '0',
+ 'No more',
+ null,
+ null
+ ],
+ [ WikitextContentTest::$sections,
+ '',
+ 'No more',
+ null,
+ null
+ ],
+ [ WikitextContentTest::$sections,
+ '2',
+ "== TEST ==\nmore fun",
+ null,
+ null
+ ],
+ [ WikitextContentTest::$sections,
+ '8',
+ 'No more',
+ null,
+ null
+ ],
+ [ WikitextContentTest::$sections,
+ 'new',
+ 'No more',
+ 'New',
+ null
+ ],
+ ];
+ }
+
+ /**
+ * @covers JavaScriptContent::addSectionHeader
+ */
+ public function testAddSectionHeader() {
+ $content = $this->newContent( 'hello world' );
+ $c = $content->addSectionHeader( 'test' );
+
+ $this->assertTrue( $content->equals( $c ) );
+ }
+
+ // XXX: currently, preSaveTransform is applied to scripts. this may change or become optional.
+ public static function dataPreSaveTransform() {
+ return [
+ [ 'hello this is ~~~',
+ "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
+ ],
+ [ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ ],
+ [ " Foo \n ",
+ " Foo",
+ ],
+ ];
+ }
+
+ public static function dataPreloadTransform() {
+ return [
+ [
+ 'hello this is ~~~',
+ 'hello this is ~~~',
+ ],
+ [
+ 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
+ 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
+ ],
+ ];
+ }
+
+ public static function dataGetRedirectTarget() {
+ return [
+ [ '#REDIRECT [[Test]]',
+ null,
+ ],
+ [ '#REDIRECT Test',
+ null,
+ ],
+ [ '* #REDIRECT [[Test]]',
+ null,
+ ],
+ ];
+ }
+
+ public static function dataIsCountable() {
+ return [
+ [ '',
+ null,
+ 'any',
+ true
+ ],
+ [ 'Foo',
+ null,
+ 'any',
+ true
+ ],
+ [ 'Foo',
+ null,
+ 'link',
+ false
+ ],
+ [ 'Foo [[bar]]',
+ null,
+ 'link',
+ false
+ ],
+ [ 'Foo',
+ true,
+ 'link',
+ false
+ ],
+ [ 'Foo [[bar]]',
+ false,
+ 'link',
+ false
+ ],
+ [ '#REDIRECT [[bar]]',
+ true,
+ 'any',
+ true
+ ],
+ [ '#REDIRECT [[bar]]',
+ true,
+ 'link',
+ false
+ ],
+ ];
+ }
+
+ public static function dataGetTextForSummary() {
+ return [
+ [ "hello\nworld.",
+ 16,
+ 'hello world.',
+ ],
+ [ 'hello world.',
+ 8,
+ 'hello...',
+ ],
+ [ '[[hello world]].',
+ 8,
+ '[[hel...',
+ ],
+ ];
+ }
+
+ /**
+ * @covers JavaScriptContent::matchMagicWord
+ */
+ public function testMatchMagicWord() {
+ $mw = MagicWord::get( "staticredirect" );
+
+ $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" );
+ $this->assertFalse(
+ $content->matchMagicWord( $mw ),
+ "should not have matched magic word, since it's not wikitext"
+ );
+ }
+
+ /**
+ * @covers JavaScriptContent::updateRedirect
+ * @dataProvider provideUpdateRedirect
+ */
+ public function testUpdateRedirect( $oldText, $expectedText ) {
+ $this->setMwGlobals( [
+ 'wgServer' => '//example.org',
+ 'wgScriptPath' => '/w',
+ 'wgScript' => '/w/index.php',
+ 'wgResourceBasePath' => '/w',
+ ] );
+ $target = Title::newFromText( "testUpdateRedirect_target" );
+
+ $content = new JavaScriptContent( $oldText );
+ $newContent = $content->updateRedirect( $target );
+
+ $this->assertEquals( $expectedText, $newContent->getNativeData() );
+ }
+
+ public static function provideUpdateRedirect() {
+ // phpcs:disable Generic.Files.LineLength
+ return [
+ [
+ '#REDIRECT [[Someplace]]',
+ '#REDIRECT [[Someplace]]',
+ ],
+ [
+ '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");',
+ '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=TestUpdateRedirect_target\u0026action=raw\u0026ctype=text/javascript");'
+ ]
+ ];
+ // phpcs:enable
+ }
+
+ /**
+ * @covers JavaScriptContent::getModel
+ */
+ public function testGetModel() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getModel() );
+ }
+
+ /**
+ * @covers JavaScriptContent::getContentHandler
+ */
+ public function testGetContentHandler() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getContentHandler()->getModelID() );
+ }
+
+ public static function dataEquals() {
+ return [
+ [ new JavaScriptContent( "hallo" ), null, false ],
+ [ new JavaScriptContent( "hallo" ), new JavaScriptContent( "hallo" ), true ],
+ [ new JavaScriptContent( "hallo" ), new CssContent( "hallo" ), false ],
+ [ new JavaScriptContent( "hallo" ), new JavaScriptContent( "HALLO" ), false ],
+ ];
+ }
+
+ /**
+ * @covers JavaScriptContent::getRedirectTarget
+ * @dataProvider provideGetRedirectTarget
+ */
+ public function testGetRedirectTarget( $title, $text ) {
+ $this->setMwGlobals( [
+ 'wgServer' => '//example.org',
+ 'wgScriptPath' => '/w',
+ 'wgScript' => '/w/index.php',
+ 'wgResourceBasePath' => '/w',
+ ] );
+ $content = new JavaScriptContent( $text );
+ $target = $content->getRedirectTarget();
+ $this->assertEquals( $title, $target ? $target->getPrefixedText() : null );
+ }
+
+ /**
+ * Keep this in sync with JavaScriptContentHandlerTest::provideMakeRedirectContent()
+ */
+ public static function provideGetRedirectTarget() {
+ // phpcs:disable Generic.Files.LineLength
+ return [
+ [
+ 'MediaWiki:MonoBook.js',
+ '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");'
+ ],
+ [
+ 'User:FooBar/common.js',
+ '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=User:FooBar/common.js\u0026action=raw\u0026ctype=text/javascript");'
+ ],
+ [
+ 'Gadget:FooBaz.js',
+ '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");'
+ ],
+ // No #REDIRECT comment
+ [
+ null,
+ 'mw.loader.load("//example.org/w/index.php?title=MediaWiki:NoRedirect.js\u0026action=raw\u0026ctype=text/javascript");'
+ ],
+ // Different domain
+ [
+ null,
+ '/* #REDIRECT */mw.loader.load("//example.com/w/index.php?title=MediaWiki:OtherWiki.js\u0026action=raw\u0026ctype=text/javascript");'
+ ],
+ ];
+ // phpcs:enable
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/JsonContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/JsonContentHandlerTest.php
new file mode 100644
index 00000000..abfb6733
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/JsonContentHandlerTest.php
@@ -0,0 +1,14 @@
+<?php
+
+class JsonContentHandlerTest extends MediaWikiTestCase {
+
+ /**
+ * @covers JsonContentHandler::makeEmptyContent
+ */
+ public function testMakeEmptyContent() {
+ $handler = new JsonContentHandler();
+ $content = $handler->makeEmptyContent();
+ $this->assertInstanceOf( JsonContent::class, $content );
+ $this->assertTrue( $content->isValid() );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/JsonContentTest.php b/www/wiki/tests/phpunit/includes/content/JsonContentTest.php
new file mode 100644
index 00000000..7cddbad2
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/JsonContentTest.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @author Addshore
+ * @covers JsonContent
+ */
+class JsonContentTest extends MediaWikiLangTestCase {
+
+ public static function provideValidConstruction() {
+ return [
+ [ 'foo', false, null ],
+ [ '[]', true, [] ],
+ [ '{}', true, (object)[] ],
+ [ '""', true, '' ],
+ [ '"0"', true, '0' ],
+ [ '"bar"', true, 'bar' ],
+ [ '0', true, '0' ],
+ [ '{ "0": "bar" }', true, (object)[ 'bar' ] ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideValidConstruction
+ */
+ public function testIsValid( $text, $isValid, $expected ) {
+ $obj = new JsonContent( $text, CONTENT_MODEL_JSON );
+ $this->assertEquals( $isValid, $obj->isValid() );
+ $this->assertEquals( $expected, $obj->getData()->getValue() );
+ }
+
+ public static function provideDataToEncode() {
+ return [
+ [
+ // Round-trip empty array
+ '[]',
+ '[]',
+ ],
+ [
+ // Round-trip empty object
+ '{}',
+ '{}',
+ ],
+ [
+ // Round-trip empty array/object (nested)
+ '{ "foo": {}, "bar": [] }',
+ "{\n \"foo\": {},\n \"bar\": []\n}",
+ ],
+ [
+ '{ "foo": "bar" }',
+ "{\n \"foo\": \"bar\"\n}",
+ ],
+ [
+ '{ "foo": 1000 }',
+ "{\n \"foo\": 1000\n}",
+ ],
+ [
+ '{ "foo": 1000, "0": "bar" }',
+ "{\n \"foo\": 1000,\n \"0\": \"bar\"\n}",
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideDataToEncode
+ */
+ public function testBeautifyJson( $input, $beautified ) {
+ $obj = new JsonContent( $input );
+ $this->assertEquals( $beautified, $obj->beautifyJSON() );
+ }
+
+ /**
+ * @dataProvider provideDataToEncode
+ */
+ public function testPreSaveTransform( $input, $transformed ) {
+ $obj = new JsonContent( $input );
+ $newObj = $obj->preSaveTransform(
+ $this->getMockTitle(),
+ $this->getMockUser(),
+ $this->getMockParserOptions()
+ );
+ $this->assertTrue( $newObj->equals( new JsonContent( $transformed ) ) );
+ }
+
+ private function getMockTitle() {
+ return $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getMockUser() {
+ return $this->getMockBuilder( User::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+ private function getMockParserOptions() {
+ return $this->getMockBuilder( ParserOptions::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public static function provideDataAndParserText() {
+ return [
+ [
+ [],
+ '<table class="mw-json"><tbody><tr><td>' .
+ '<table class="mw-json"><tbody><tr><td class="mw-json-empty">Empty array</td></tr>'
+ . '</tbody></table></td></tr></tbody></table>'
+ ],
+ [
+ (object)[],
+ '<table class="mw-json"><tbody><tr><td class="mw-json-empty">Empty object</td></tr>' .
+ '</tbody></table>'
+ ],
+ [
+ (object)[ 'foo' ],
+ '<table class="mw-json"><tbody><tr><th>0</th><td class="value">"foo"</td></tr>' .
+ '</tbody></table>'
+ ],
+ [
+ (object)[ 'foo', 'bar' ],
+ '<table class="mw-json"><tbody><tr><th>0</th><td class="value">"foo"</td></tr>' .
+ '<tr><th>1</th><td class="value">"bar"</td></tr></tbody></table>'
+ ],
+ [
+ (object)[ 'baz' => 'foo', 'bar' ],
+ '<table class="mw-json"><tbody><tr><th>baz</th><td class="value">"foo"</td></tr>' .
+ '<tr><th>0</th><td class="value">"bar"</td></tr></tbody></table>'
+ ],
+ [
+ (object)[ 'baz' => 1000, 'bar' ],
+ '<table class="mw-json"><tbody><tr><th>baz</th><td class="value">1000</td></tr>' .
+ '<tr><th>0</th><td class="value">"bar"</td></tr></tbody></table>'
+ ],
+ [
+ (object)[ '<script>alert("evil!")</script>' ],
+ '<table class="mw-json"><tbody><tr><th>0</th><td class="value">"' .
+ '&lt;script>alert("evil!")&lt;/script>"' .
+ '</td></tr></tbody></table>',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideDataAndParserText
+ */
+ public function testFillParserOutput( $data, $expected ) {
+ $obj = new JsonContent( FormatJson::encode( $data ) );
+ $parserOutput = $obj->getParserOutput( $this->getMockTitle(), null, null, true );
+ $this->assertInstanceOf( ParserOutput::class, $parserOutput );
+ $this->assertEquals( $expected, $parserOutput->getText() );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/TextContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/TextContentHandlerTest.php
new file mode 100644
index 00000000..6d0a3d5c
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/TextContentHandlerTest.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @group ContentHandler
+ */
+class TextContentHandlerTest extends MediaWikiLangTestCase {
+ /**
+ * @covers TextContentHandler::supportsDirectEditing
+ */
+ public function testSupportsDirectEditing() {
+ $handler = new TextContentHandler();
+ $this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' );
+ }
+
+ /**
+ * @covers SearchEngine::makeSearchFieldMapping
+ * @covers ContentHandler::getFieldsForSearchIndex
+ */
+ public function testFieldsForIndex() {
+ $handler = new TextContentHandler();
+
+ $mockEngine = $this->createMock( SearchEngine::class );
+
+ $mockEngine->expects( $this->atLeastOnce() )
+ ->method( 'makeSearchFieldMapping' )
+ ->willReturnCallback( function ( $name, $type ) {
+ $mockField =
+ $this->getMockBuilder( SearchIndexFieldDefinition::class )
+ ->setConstructorArgs( [ $name, $type ] )
+ ->getMock();
+ $mockField->expects( $this->atLeastOnce() )->method( 'getMapping' )->willReturn( [
+ 'testData' => 'test',
+ 'name' => $name,
+ 'type' => $type,
+ ] );
+ return $mockField;
+ } );
+
+ /**
+ * @var $mockEngine SearchEngine
+ */
+ $fields = $handler->getFieldsForSearchIndex( $mockEngine );
+ $mappedFields = [];
+ foreach ( $fields as $name => $field ) {
+ $this->assertInstanceOf( SearchIndexField::class, $field );
+ /**
+ * @var $field SearchIndexField
+ */
+ $mappedFields[$name] = $field->getMapping( $mockEngine );
+ }
+ $this->assertArrayHasKey( 'language', $mappedFields );
+ $this->assertEquals( 'test', $mappedFields['language']['testData'] );
+ $this->assertEquals( 'language', $mappedFields['language']['name'] );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/TextContentTest.php b/www/wiki/tests/phpunit/includes/content/TextContentTest.php
new file mode 100644
index 00000000..406bc96b
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/TextContentTest.php
@@ -0,0 +1,477 @@
+<?php
+
+/**
+ * @group ContentHandler
+ * @group Database
+ * ^--- needed, because we do need the database to test link updates
+ */
+class TextContentTest extends MediaWikiLangTestCase {
+ protected $context;
+
+ protected function setUp() {
+ parent::setUp();
+
+ // Anon user
+ $user = new User();
+ $user->setName( '127.0.0.1' );
+
+ $this->context = new RequestContext( new FauxRequest() );
+ $this->context->setTitle( Title::newFromText( 'Test' ) );
+ $this->context->setUser( $user );
+
+ $this->setMwGlobals( [
+ 'wgUser' => $user,
+ 'wgTextModelsToParse' => [
+ CONTENT_MODEL_WIKITEXT,
+ CONTENT_MODEL_CSS,
+ CONTENT_MODEL_JAVASCRIPT,
+ ],
+ 'wgUseTidy' => false,
+ 'wgCapitalLinks' => true,
+ 'wgHooks' => [], // bypass hook ContentGetParserOutput that force custom rendering
+ ] );
+
+ MWTidy::destroySingleton();
+ }
+
+ protected function tearDown() {
+ MWTidy::destroySingleton();
+ parent::tearDown();
+ }
+
+ public function newContent( $text ) {
+ return new TextContent( $text );
+ }
+
+ public static function dataGetParserOutput() {
+ return [
+ [
+ 'TextContentTest_testGetParserOutput',
+ CONTENT_MODEL_TEXT,
+ "hello ''world'' & [[stuff]]\n", "hello ''world'' &amp; [[stuff]]",
+ [
+ 'Links' => []
+ ]
+ ],
+ // TODO: more...?
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetParserOutput
+ * @covers TextContent::getParserOutput
+ */
+ public function testGetParserOutput( $title, $model, $text, $expectedHtml,
+ $expectedFields = null
+ ) {
+ $title = Title::newFromText( $title );
+ $content = ContentHandler::makeContent( $text, $title, $model );
+
+ $po = $content->getParserOutput( $title );
+
+ $html = $po->getText();
+ $html = preg_replace( '#<!--.*?-->#sm', '', $html ); // strip comments
+
+ $this->assertEquals( $expectedHtml, trim( $html ) );
+
+ if ( $expectedFields ) {
+ foreach ( $expectedFields as $field => $exp ) {
+ $f = 'get' . ucfirst( $field );
+ $v = call_user_func( [ $po, $f ] );
+
+ if ( is_array( $exp ) ) {
+ $this->assertArrayEquals( $exp, $v );
+ } else {
+ $this->assertEquals( $exp, $v );
+ }
+ }
+ }
+
+ // TODO: assert more properties
+ }
+
+ public static function dataPreSaveTransform() {
+ return [
+ [
+ # 0: no signature resolution
+ 'hello this is ~~~',
+ 'hello this is ~~~',
+ ],
+ [
+ # 1: rtrim
+ " Foo \n ",
+ ' Foo',
+ ],
+ [
+ # 2: newline normalization
+ "LF\n\nCRLF\r\n\r\nCR\r\rEND",
+ "LF\n\nCRLF\n\nCR\n\nEND",
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataPreSaveTransform
+ * @covers TextContent::preSaveTransform
+ */
+ public function testPreSaveTransform( $text, $expected ) {
+ global $wgContLang;
+
+ $options = ParserOptions::newFromUserAndLang( $this->context->getUser(), $wgContLang );
+
+ $content = $this->newContent( $text );
+ $content = $content->preSaveTransform(
+ $this->context->getTitle(),
+ $this->context->getUser(),
+ $options
+ );
+
+ $this->assertEquals( $expected, $content->getNativeData() );
+ }
+
+ public static function dataPreloadTransform() {
+ return [
+ [
+ 'hello this is ~~~',
+ 'hello this is ~~~',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataPreloadTransform
+ * @covers TextContent::preloadTransform
+ */
+ public function testPreloadTransform( $text, $expected ) {
+ global $wgContLang;
+ $options = ParserOptions::newFromUserAndLang( $this->context->getUser(), $wgContLang );
+
+ $content = $this->newContent( $text );
+ $content = $content->preloadTransform( $this->context->getTitle(), $options );
+
+ $this->assertEquals( $expected, $content->getNativeData() );
+ }
+
+ public static function dataGetRedirectTarget() {
+ return [
+ [ '#REDIRECT [[Test]]',
+ null,
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetRedirectTarget
+ * @covers TextContent::getRedirectTarget
+ */
+ public function testGetRedirectTarget( $text, $expected ) {
+ $content = $this->newContent( $text );
+ $t = $content->getRedirectTarget();
+
+ if ( is_null( $expected ) ) {
+ $this->assertNull( $t, "text should not have generated a redirect target: $text" );
+ } else {
+ $this->assertEquals( $expected, $t->getPrefixedText() );
+ }
+ }
+
+ /**
+ * @dataProvider dataGetRedirectTarget
+ * @covers TextContent::isRedirect
+ */
+ public function testIsRedirect( $text, $expected ) {
+ $content = $this->newContent( $text );
+
+ $this->assertEquals( !is_null( $expected ), $content->isRedirect() );
+ }
+
+ public static function dataIsCountable() {
+ return [
+ [ '',
+ null,
+ 'any',
+ true
+ ],
+ [ 'Foo',
+ null,
+ 'any',
+ true
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataIsCountable
+ * @covers TextContent::isCountable
+ */
+ public function testIsCountable( $text, $hasLinks, $mode, $expected ) {
+ $this->setMwGlobals( 'wgArticleCountMethod', $mode );
+
+ $content = $this->newContent( $text );
+
+ $v = $content->isCountable( $hasLinks, $this->context->getTitle() );
+
+ $this->assertEquals(
+ $expected,
+ $v,
+ 'isCountable() returned unexpected value ' . var_export( $v, true )
+ . ' instead of ' . var_export( $expected, true )
+ . " in mode `$mode` for text \"$text\""
+ );
+ }
+
+ public static function dataGetTextForSummary() {
+ return [
+ [ "hello\nworld.",
+ 16,
+ 'hello world.',
+ ],
+ [ 'hello world.',
+ 8,
+ 'hello...',
+ ],
+ [ '[[hello world]].',
+ 8,
+ '[[hel...',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetTextForSummary
+ * @covers TextContent::getTextForSummary
+ */
+ public function testGetTextForSummary( $text, $maxlength, $expected ) {
+ $content = $this->newContent( $text );
+
+ $this->assertEquals( $expected, $content->getTextForSummary( $maxlength ) );
+ }
+
+ /**
+ * @covers TextContent::getTextForSearchIndex
+ */
+ public function testGetTextForSearchIndex() {
+ $content = $this->newContent( 'hello world.' );
+
+ $this->assertEquals( 'hello world.', $content->getTextForSearchIndex() );
+ }
+
+ /**
+ * @covers TextContent::copy
+ */
+ public function testCopy() {
+ $content = $this->newContent( 'hello world.' );
+ $copy = $content->copy();
+
+ $this->assertTrue( $content->equals( $copy ), 'copy must be equal to original' );
+ $this->assertEquals( 'hello world.', $copy->getNativeData() );
+ }
+
+ /**
+ * @covers TextContent::getSize
+ */
+ public function testGetSize() {
+ $content = $this->newContent( 'hello world.' );
+
+ $this->assertEquals( 12, $content->getSize() );
+ }
+
+ /**
+ * @covers TextContent::getNativeData
+ */
+ public function testGetNativeData() {
+ $content = $this->newContent( 'hello world.' );
+
+ $this->assertEquals( 'hello world.', $content->getNativeData() );
+ }
+
+ /**
+ * @covers TextContent::getWikitextForTransclusion
+ */
+ public function testGetWikitextForTransclusion() {
+ $content = $this->newContent( 'hello world.' );
+
+ $this->assertEquals( 'hello world.', $content->getWikitextForTransclusion() );
+ }
+
+ /**
+ * @covers TextContent::getModel
+ */
+ public function testGetModel() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_TEXT, $content->getModel() );
+ }
+
+ /**
+ * @covers TextContent::getContentHandler
+ */
+ public function testGetContentHandler() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_TEXT, $content->getContentHandler()->getModelID() );
+ }
+
+ public static function dataIsEmpty() {
+ return [
+ [ '', true ],
+ [ ' ', false ],
+ [ '0', false ],
+ [ 'hallo welt.', false ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataIsEmpty
+ * @covers TextContent::isEmpty
+ */
+ public function testIsEmpty( $text, $empty ) {
+ $content = $this->newContent( $text );
+
+ $this->assertEquals( $empty, $content->isEmpty() );
+ }
+
+ public static function dataEquals() {
+ return [
+ [ new TextContent( "hallo" ), null, false ],
+ [ new TextContent( "hallo" ), new TextContent( "hallo" ), true ],
+ [ new TextContent( "hallo" ), new JavaScriptContent( "hallo" ), false ],
+ [ new TextContent( "hallo" ), new WikitextContent( "hallo" ), false ],
+ [ new TextContent( "hallo" ), new TextContent( "HALLO" ), false ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataEquals
+ * @covers TextContent::equals
+ */
+ public function testEquals( Content $a, Content $b = null, $equal = false ) {
+ $this->assertEquals( $equal, $a->equals( $b ) );
+ }
+
+ public static function dataGetDeletionUpdates() {
+ return [
+ [ "TextContentTest_testGetSecondaryDataUpdates_1",
+ CONTENT_MODEL_TEXT, "hello ''world''\n",
+ []
+ ],
+ [ "TextContentTest_testGetSecondaryDataUpdates_2",
+ CONTENT_MODEL_TEXT, "hello [[world test 21344]]\n",
+ []
+ ],
+ // TODO: more...?
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetDeletionUpdates
+ * @covers TextContent::getDeletionUpdates
+ */
+ public function testDeletionUpdates( $title, $model, $text, $expectedStuff ) {
+ $ns = $this->getDefaultWikitextNS();
+ $title = Title::newFromText( $title, $ns );
+
+ $content = ContentHandler::makeContent( $text, $title, $model );
+
+ $page = WikiPage::factory( $title );
+ $page->doEditContent( $content, '' );
+
+ $updates = $content->getDeletionUpdates( $page );
+
+ // make updates accessible by class name
+ foreach ( $updates as $update ) {
+ $class = get_class( $update );
+ $updates[$class] = $update;
+ }
+
+ if ( !$expectedStuff ) {
+ $this->assertTrue( true ); // make phpunit happy
+ return;
+ }
+
+ foreach ( $expectedStuff as $class => $fieldValues ) {
+ $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" );
+
+ $update = $updates[$class];
+
+ foreach ( $fieldValues as $field => $value ) {
+ $v = $update->$field; # if the field doesn't exist, just crash and burn
+ $this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" );
+ }
+ }
+
+ $page->doDeleteArticle( '' );
+ }
+
+ public static function provideConvert() {
+ return [
+ [ // #0
+ 'Hallo Welt',
+ CONTENT_MODEL_WIKITEXT,
+ 'lossless',
+ 'Hallo Welt'
+ ],
+ [ // #1
+ 'Hallo Welt',
+ CONTENT_MODEL_WIKITEXT,
+ 'lossless',
+ 'Hallo Welt'
+ ],
+ [ // #1
+ 'Hallo Welt',
+ CONTENT_MODEL_CSS,
+ 'lossless',
+ 'Hallo Welt'
+ ],
+ [ // #1
+ 'Hallo Welt',
+ CONTENT_MODEL_JAVASCRIPT,
+ 'lossless',
+ 'Hallo Welt'
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideConvert
+ * @covers TextContent::convert
+ */
+ public function testConvert( $text, $model, $lossy, $expectedNative ) {
+ $content = $this->newContent( $text );
+
+ $converted = $content->convert( $model, $lossy );
+
+ if ( $expectedNative === false ) {
+ $this->assertFalse( $converted, "conversion to $model was expected to fail!" );
+ } else {
+ $this->assertInstanceOf( Content::class, $converted );
+ $this->assertEquals( $expectedNative, $converted->getNativeData() );
+ }
+ }
+
+ /**
+ * @covers TextContent::normalizeLineEndings
+ * @dataProvider provideNormalizeLineEndings
+ */
+ public function testNormalizeLineEndings( $input, $expected ) {
+ $this->assertEquals( $expected, TextContent::normalizeLineEndings( $input ) );
+ }
+
+ public static function provideNormalizeLineEndings() {
+ return [
+ [
+ "Foo\r\nbar",
+ "Foo\nbar"
+ ],
+ [
+ "Foo\rbar",
+ "Foo\nbar"
+ ],
+ [
+ "Foobar\n ",
+ "Foobar"
+ ]
+ ];
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/content/WikitextContentHandlerTest.php b/www/wiki/tests/phpunit/includes/content/WikitextContentHandlerTest.php
new file mode 100644
index 00000000..59984d85
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/WikitextContentHandlerTest.php
@@ -0,0 +1,365 @@
+<?php
+
+/**
+ * @group ContentHandler
+ */
+class WikitextContentHandlerTest extends MediaWikiLangTestCase {
+ /**
+ * @var ContentHandler
+ */
+ private $handler;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
+ }
+
+ /**
+ * @covers WikitextContentHandler::serializeContent
+ */
+ public function testSerializeContent() {
+ $content = new WikitextContent( 'hello world' );
+
+ $this->assertEquals( 'hello world', $this->handler->serializeContent( $content ) );
+ $this->assertEquals(
+ 'hello world',
+ $this->handler->serializeContent( $content, CONTENT_FORMAT_WIKITEXT )
+ );
+
+ try {
+ $this->handler->serializeContent( $content, 'dummy/foo' );
+ $this->fail( "serializeContent() should have failed on unknown format" );
+ } catch ( MWException $e ) {
+ // ok, as expected
+ }
+ }
+
+ /**
+ * @covers WikitextContentHandler::unserializeContent
+ */
+ public function testUnserializeContent() {
+ $content = $this->handler->unserializeContent( 'hello world' );
+ $this->assertEquals( 'hello world', $content->getNativeData() );
+
+ $content = $this->handler->unserializeContent( 'hello world', CONTENT_FORMAT_WIKITEXT );
+ $this->assertEquals( 'hello world', $content->getNativeData() );
+
+ try {
+ $this->handler->unserializeContent( 'hello world', 'dummy/foo' );
+ $this->fail( "unserializeContent() should have failed on unknown format" );
+ } catch ( MWException $e ) {
+ // ok, as expected
+ }
+ }
+
+ /**
+ * @covers WikitextContentHandler::makeEmptyContent
+ */
+ public function testMakeEmptyContent() {
+ $content = $this->handler->makeEmptyContent();
+
+ $this->assertTrue( $content->isEmpty() );
+ $this->assertEquals( '', $content->getNativeData() );
+ }
+
+ public static function dataIsSupportedFormat() {
+ return [
+ [ null, true ],
+ [ CONTENT_FORMAT_WIKITEXT, true ],
+ [ 99887766, false ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideMakeRedirectContent
+ * @param Title|string $title Title object or string for Title::newFromText()
+ * @param string $expected Serialized form of the content object built
+ * @covers WikitextContentHandler::makeRedirectContent
+ */
+ public function testMakeRedirectContent( $title, $expected ) {
+ global $wgContLang;
+ $wgContLang->resetNamespaces();
+
+ MagicWord::clearCache();
+
+ if ( is_string( $title ) ) {
+ $title = Title::newFromText( $title );
+ }
+ $content = $this->handler->makeRedirectContent( $title );
+ $this->assertEquals( $expected, $content->serialize() );
+ }
+
+ public static function provideMakeRedirectContent() {
+ return [
+ [ 'Hello', '#REDIRECT [[Hello]]' ],
+ [ 'Template:Hello', '#REDIRECT [[Template:Hello]]' ],
+ [ 'Hello#section', '#REDIRECT [[Hello#section]]' ],
+ [ 'user:john_doe#section', '#REDIRECT [[User:John doe#section]]' ],
+ [ 'MEDIAWIKI:FOOBAR', '#REDIRECT [[MediaWiki:FOOBAR]]' ],
+ [ 'Category:Foo', '#REDIRECT [[:Category:Foo]]' ],
+ [ Title::makeTitle( NS_MAIN, 'en:Foo' ), '#REDIRECT [[en:Foo]]' ],
+ [ Title::makeTitle( NS_MAIN, 'Foo', '', 'en' ), '#REDIRECT [[:en:Foo]]' ],
+ [
+ Title::makeTitle( NS_MAIN, 'Bar', 'fragment', 'google' ),
+ '#REDIRECT [[google:Bar#fragment]]'
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataIsSupportedFormat
+ * @covers WikitextContentHandler::isSupportedFormat
+ */
+ public function testIsSupportedFormat( $format, $supported ) {
+ $this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) );
+ }
+
+ /**
+ * @covers WikitextContentHandler::supportsDirectEditing
+ */
+ public function testSupportsDirectEditing() {
+ $handler = new WikiTextContentHandler();
+ $this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' );
+ }
+
+ public static function dataMerge3() {
+ return [
+ [
+ "first paragraph
+
+ second paragraph\n",
+
+ "FIRST paragraph
+
+ second paragraph\n",
+
+ "first paragraph
+
+ SECOND paragraph\n",
+
+ "FIRST paragraph
+
+ SECOND paragraph\n",
+ ],
+
+ [ "first paragraph
+ second paragraph\n",
+
+ "Bla bla\n",
+
+ "Blubberdibla\n",
+
+ false,
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataMerge3
+ * @covers WikitextContentHandler::merge3
+ */
+ public function testMerge3( $old, $mine, $yours, $expected ) {
+ $this->markTestSkippedIfNoDiff3();
+
+ // test merge
+ $oldContent = new WikitextContent( $old );
+ $myContent = new WikitextContent( $mine );
+ $yourContent = new WikitextContent( $yours );
+
+ $merged = $this->handler->merge3( $oldContent, $myContent, $yourContent );
+
+ $this->assertEquals( $expected, $merged ? $merged->getNativeData() : $merged );
+ }
+
+ public static function dataGetAutosummary() {
+ return [
+ [
+ 'Hello there, world!',
+ '#REDIRECT [[Foo]]',
+ 0,
+ '/^Redirected page .*Foo/'
+ ],
+
+ [
+ null,
+ 'Hello world!',
+ EDIT_NEW,
+ '/^Created page .*Hello/'
+ ],
+
+ [
+ null,
+ '',
+ EDIT_NEW,
+ '/^Created blank page$/'
+ ],
+
+ [
+ 'Hello there, world!',
+ '',
+ 0,
+ '/^Blanked/'
+ ],
+
+ [
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
+ eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
+ voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
+ clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
+ 'Hello world!',
+ 0,
+ '/^Replaced .*Hello/'
+ ],
+
+ [
+ 'foo',
+ 'bar',
+ 0,
+ '/^$/'
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetAutosummary
+ * @covers WikitextContentHandler::getAutosummary
+ */
+ public function testGetAutosummary( $old, $new, $flags, $expected ) {
+ $oldContent = is_null( $old ) ? null : new WikitextContent( $old );
+ $newContent = is_null( $new ) ? null : new WikitextContent( $new );
+
+ $summary = $this->handler->getAutosummary( $oldContent, $newContent, $flags );
+
+ $this->assertTrue(
+ (bool)preg_match( $expected, $summary ),
+ "Autosummary didn't match expected pattern $expected: $summary"
+ );
+ }
+
+ public static function dataGetChangeTag() {
+ return [
+ [
+ null,
+ '#REDIRECT [[Foo]]',
+ 0,
+ 'mw-new-redirect'
+ ],
+
+ [
+ 'Lorem ipsum dolor',
+ '#REDIRECT [[Foo]]',
+ 0,
+ 'mw-new-redirect'
+ ],
+
+ [
+ '#REDIRECT [[Foo]]',
+ 'Lorem ipsum dolor',
+ 0,
+ 'mw-removed-redirect'
+ ],
+
+ [
+ '#REDIRECT [[Foo]]',
+ '#REDIRECT [[Bar]]',
+ 0,
+ 'mw-changed-redirect-target'
+ ],
+
+ [
+ null,
+ 'Lorem ipsum dolor',
+ EDIT_NEW,
+ null // mw-newpage is not defined as a tag
+ ],
+
+ [
+ null,
+ '',
+ EDIT_NEW,
+ null // mw-newblank is not defined as a tag
+ ],
+
+ [
+ 'Lorem ipsum dolor',
+ '',
+ 0,
+ 'mw-blank'
+ ],
+
+ [
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
+ eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
+ voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
+ clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
+ 'Ipsum',
+ 0,
+ 'mw-replace'
+ ],
+
+ [
+ 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
+ eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
+ voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
+ clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
+ 'Duis purus odio, rhoncus et finibus dapibus, facilisis ac urna. Pellentesque
+ arcu, tristique nec tempus nec, suscipit vel arcu. Sed non dolor nec ligula
+ congue tempor. Quisque pellentesque finibus orci a molestie. Nam maximus, purus
+ euismod finibus mollis, dui ante malesuada felis, dignissim rutrum diam sapien.',
+ 0,
+ null
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetChangeTag
+ * @covers WikitextContentHandler::getChangeTag
+ */
+ public function testGetChangeTag( $old, $new, $flags, $expected ) {
+ $this->setMwGlobals( 'wgSoftwareTags', [
+ 'mw-new-redirect' => true,
+ 'mw-removed-redirect' => true,
+ 'mw-changed-redirect-target' => true,
+ 'mw-newpage' => true,
+ 'mw-newblank' => true,
+ 'mw-blank' => true,
+ 'mw-replace' => true,
+ ] );
+ $oldContent = is_null( $old ) ? null : new WikitextContent( $old );
+ $newContent = is_null( $new ) ? null : new WikitextContent( $new );
+
+ $tag = $this->handler->getChangeTag( $oldContent, $newContent, $flags );
+
+ $this->assertSame( $expected, $tag );
+ }
+
+ /**
+ * @covers WikitextContentHandler::getDataForSearchIndex
+ */
+ public function testDataIndexFieldsFile() {
+ $mockEngine = $this->createMock( SearchEngine::class );
+ $title = Title::newFromText( 'Somefile.jpg', NS_FILE );
+ $page = new WikiPage( $title );
+
+ $fileHandler = $this->getMockBuilder( FileContentHandler::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'getDataForSearchIndex' ] )
+ ->getMock();
+
+ $handler = $this->getMockBuilder( WikitextContentHandler::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'getFileHandler' ] )
+ ->getMock();
+
+ $handler->method( 'getFileHandler' )->will( $this->returnValue( $fileHandler ) );
+ $fileHandler->expects( $this->once() )
+ ->method( 'getDataForSearchIndex' )
+ ->will( $this->returnValue( [ 'file_text' => 'This is file content' ] ) );
+
+ $data = $handler->getDataForSearchIndex( $page, new ParserOutput(), $mockEngine );
+ $this->assertArrayHasKey( 'file_text', $data );
+ $this->assertEquals( 'This is file content', $data['file_text'] );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/WikitextContentTest.php b/www/wiki/tests/phpunit/includes/content/WikitextContentTest.php
new file mode 100644
index 00000000..1db6aab6
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/WikitextContentTest.php
@@ -0,0 +1,443 @@
+<?php
+
+/**
+ * @group ContentHandler
+ *
+ * @group Database
+ * ^--- needed, because we do need the database to test link updates
+ */
+class WikitextContentTest extends TextContentTest {
+ public static $sections = "Intro
+
+== stuff ==
+hello world
+
+== test ==
+just a test
+
+== foo ==
+more stuff
+";
+
+ public function newContent( $text ) {
+ return new WikitextContent( $text );
+ }
+
+ public static function dataGetParserOutput() {
+ return [
+ [
+ "WikitextContentTest_testGetParserOutput",
+ CONTENT_MODEL_WIKITEXT,
+ "hello ''world''\n",
+ "<div class=\"mw-parser-output\"><p>hello <i>world</i>\n</p>\n\n\n</div>"
+ ],
+ // TODO: more...?
+ ];
+ }
+
+ public static function dataGetSecondaryDataUpdates() {
+ return [
+ [ "WikitextContentTest_testGetSecondaryDataUpdates_1",
+ CONTENT_MODEL_WIKITEXT, "hello ''world''\n",
+ [
+ LinksUpdate::class => [
+ 'mRecursive' => true,
+ 'mLinks' => []
+ ]
+ ]
+ ],
+ [ "WikitextContentTest_testGetSecondaryDataUpdates_2",
+ CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n",
+ [
+ LinksUpdate::class => [
+ 'mRecursive' => true,
+ 'mLinks' => [
+ [ 'World_test_21344' => 0 ]
+ ]
+ ]
+ ]
+ ],
+ // TODO: more...?
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetSecondaryDataUpdates
+ * @group Database
+ * @covers WikitextContent::getSecondaryDataUpdates
+ */
+ public function testGetSecondaryDataUpdates( $title, $model, $text, $expectedStuff ) {
+ $ns = $this->getDefaultWikitextNS();
+ $title = Title::newFromText( $title, $ns );
+
+ $content = ContentHandler::makeContent( $text, $title, $model );
+
+ $page = WikiPage::factory( $title );
+ $page->doEditContent( $content, '' );
+
+ $updates = $content->getSecondaryDataUpdates( $title );
+
+ // make updates accessible by class name
+ foreach ( $updates as $update ) {
+ $class = get_class( $update );
+ $updates[$class] = $update;
+ }
+
+ foreach ( $expectedStuff as $class => $fieldValues ) {
+ $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" );
+
+ $update = $updates[$class];
+
+ foreach ( $fieldValues as $field => $value ) {
+ $v = $update->$field; # if the field doesn't exist, just crash and burn
+ $this->assertEquals(
+ $value,
+ $v,
+ "unexpected value for field $field in instance of $class"
+ );
+ }
+ }
+
+ $page->doDeleteArticle( '' );
+ }
+
+ public static function dataGetSection() {
+ return [
+ [ self::$sections,
+ "0",
+ "Intro"
+ ],
+ [ self::$sections,
+ "2",
+ "== test ==
+just a test"
+ ],
+ [ self::$sections,
+ "8",
+ false
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetSection
+ * @covers WikitextContent::getSection
+ */
+ public function testGetSection( $text, $sectionId, $expectedText ) {
+ $content = $this->newContent( $text );
+
+ $sectionContent = $content->getSection( $sectionId );
+ if ( is_object( $sectionContent ) ) {
+ $sectionText = $sectionContent->getNativeData();
+ } else {
+ $sectionText = $sectionContent;
+ }
+
+ $this->assertEquals( $expectedText, $sectionText );
+ }
+
+ public static function dataReplaceSection() {
+ return [
+ [ self::$sections,
+ "0",
+ "No more",
+ null,
+ trim( preg_replace( '/^Intro/sm', 'No more', self::$sections ) )
+ ],
+ [ self::$sections,
+ "",
+ "No more",
+ null,
+ "No more"
+ ],
+ [ self::$sections,
+ "2",
+ "== TEST ==\nmore fun",
+ null,
+ trim( preg_replace(
+ '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==",
+ self::$sections
+ ) )
+ ],
+ [ self::$sections,
+ "8",
+ "No more",
+ null,
+ self::$sections
+ ],
+ [ self::$sections,
+ "new",
+ "No more",
+ "New",
+ trim( self::$sections ) . "\n\n\n== New ==\n\nNo more"
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataReplaceSection
+ * @covers WikitextContent::replaceSection
+ */
+ public function testReplaceSection( $text, $section, $with, $sectionTitle, $expected ) {
+ $content = $this->newContent( $text );
+ $c = $content->replaceSection( $section, $this->newContent( $with ), $sectionTitle );
+
+ $this->assertEquals( $expected, is_null( $c ) ? null : $c->getNativeData() );
+ }
+
+ /**
+ * @covers WikitextContent::addSectionHeader
+ */
+ public function testAddSectionHeader() {
+ $content = $this->newContent( 'hello world' );
+ $content = $content->addSectionHeader( 'test' );
+
+ $this->assertEquals( "== test ==\n\nhello world", $content->getNativeData() );
+ }
+
+ public static function dataPreSaveTransform() {
+ return [
+ [ 'hello this is ~~~',
+ "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
+ ],
+ [ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ ],
+ [ // rtrim
+ " Foo \n ",
+ " Foo",
+ ],
+ ];
+ }
+
+ public static function dataPreloadTransform() {
+ return [
+ [
+ 'hello this is ~~~',
+ "hello this is ~~~",
+ ],
+ [
+ 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
+ 'hello \'\'this\'\' is bar',
+ ],
+ ];
+ }
+
+ public static function dataGetRedirectTarget() {
+ return [
+ [ '#REDIRECT [[Test]]',
+ 'Test',
+ ],
+ [ '#REDIRECT Test',
+ null,
+ ],
+ [ '* #REDIRECT [[Test]]',
+ null,
+ ],
+ ];
+ }
+
+ public static function dataGetTextForSummary() {
+ return [
+ [ "hello\nworld.",
+ 16,
+ 'hello world.',
+ ],
+ [ 'hello world.',
+ 8,
+ 'hello...',
+ ],
+ [ '[[hello world]].',
+ 8,
+ 'hel...',
+ ],
+ ];
+ }
+
+ public static function dataIsCountable() {
+ return [
+ [ '',
+ null,
+ 'any',
+ true
+ ],
+ [ 'Foo',
+ null,
+ 'any',
+ true
+ ],
+ [ 'Foo',
+ null,
+ 'link',
+ false
+ ],
+ [ 'Foo [[bar]]',
+ null,
+ 'link',
+ true
+ ],
+ [ 'Foo',
+ true,
+ 'link',
+ true
+ ],
+ [ 'Foo [[bar]]',
+ false,
+ 'link',
+ false
+ ],
+ [ '#REDIRECT [[bar]]',
+ true,
+ 'any',
+ false
+ ],
+ [ '#REDIRECT [[bar]]',
+ true,
+ 'link',
+ false
+ ],
+ ];
+ }
+
+ /**
+ * @covers WikitextContent::matchMagicWord
+ */
+ public function testMatchMagicWord() {
+ $mw = MagicWord::get( "staticredirect" );
+
+ $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" );
+ $this->assertTrue( $content->matchMagicWord( $mw ), "should have matched magic word" );
+
+ $content = $this->newContent( "#REDIRECT [[FOO]]" );
+ $this->assertFalse(
+ $content->matchMagicWord( $mw ),
+ "should not have matched magic word"
+ );
+ }
+
+ /**
+ * @covers WikitextContent::updateRedirect
+ */
+ public function testUpdateRedirect() {
+ $target = Title::newFromText( "testUpdateRedirect_target" );
+
+ // test with non-redirect page
+ $content = $this->newContent( "hello world." );
+ $newContent = $content->updateRedirect( $target );
+
+ $this->assertTrue( $content->equals( $newContent ), "content should be unchanged" );
+
+ // test with actual redirect
+ $content = $this->newContent( "#REDIRECT [[Someplace]]" );
+ $newContent = $content->updateRedirect( $target );
+
+ $this->assertFalse( $content->equals( $newContent ), "content should have changed" );
+ $this->assertTrue( $newContent->isRedirect(), "new content should be a redirect" );
+
+ $this->assertEquals(
+ $target->getFullText(),
+ $newContent->getRedirectTarget()->getFullText()
+ );
+ }
+
+ /**
+ * @covers WikitextContent::getModel
+ */
+ public function testGetModel() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getModel() );
+ }
+
+ /**
+ * @covers WikitextContent::getContentHandler
+ */
+ public function testGetContentHandler() {
+ $content = $this->newContent( "hello world." );
+
+ $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getContentHandler()->getModelID() );
+ }
+
+ public function testRedirectParserOption() {
+ $title = Title::newFromText( 'testRedirectParserOption' );
+
+ // Set up hook and its reporting variables
+ $wikitext = null;
+ $redirectTarget = null;
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'InternalParseBeforeLinks' => [
+ function ( &$parser, &$text, &$stripState ) use ( &$wikitext, &$redirectTarget ) {
+ $wikitext = $text;
+ $redirectTarget = $parser->getOptions()->getRedirectTarget();
+ }
+ ]
+ ] );
+
+ // Test with non-redirect page
+ $wikitext = false;
+ $redirectTarget = false;
+ $content = $this->newContent( 'hello world.' );
+ $options = $content->getContentHandler()->makeParserOptions( 'canonical' );
+ $options->setRedirectTarget( $title );
+ $content->getParserOutput( $title, null, $options );
+ $this->assertEquals( 'hello world.', $wikitext,
+ 'Wikitext passed to hook was not as expected'
+ );
+ $this->assertEquals( null, $redirectTarget, 'Redirect seen in hook was not null' );
+ $this->assertEquals( $title, $options->getRedirectTarget(),
+ 'ParserOptions\' redirectTarget was changed'
+ );
+
+ // Test with a redirect page
+ $wikitext = false;
+ $redirectTarget = false;
+ $content = $this->newContent(
+ "#REDIRECT [[TestRedirectParserOption/redir]]\nhello redirect."
+ );
+ $options = $content->getContentHandler()->makeParserOptions( 'canonical' );
+ $content->getParserOutput( $title, null, $options );
+ $this->assertEquals(
+ 'hello redirect.',
+ $wikitext,
+ 'Wikitext passed to hook was not as expected'
+ );
+ $this->assertNotEquals(
+ null,
+ $redirectTarget,
+ 'Redirect seen in hook was null' );
+ $this->assertEquals(
+ 'TestRedirectParserOption/redir',
+ $redirectTarget->getFullText(),
+ 'Redirect seen in hook was not the expected title'
+ );
+ $this->assertEquals(
+ null,
+ $options->getRedirectTarget(),
+ 'ParserOptions\' redirectTarget was changed'
+ );
+ }
+
+ public static function dataEquals() {
+ return [
+ [ new WikitextContent( "hallo" ), null, false ],
+ [ new WikitextContent( "hallo" ), new WikitextContent( "hallo" ), true ],
+ [ new WikitextContent( "hallo" ), new JavaScriptContent( "hallo" ), false ],
+ [ new WikitextContent( "hallo" ), new TextContent( "hallo" ), false ],
+ [ new WikitextContent( "hallo" ), new WikitextContent( "HALLO" ), false ],
+ ];
+ }
+
+ public static function dataGetDeletionUpdates() {
+ return [
+ [ "WikitextContentTest_testGetSecondaryDataUpdates_1",
+ CONTENT_MODEL_WIKITEXT, "hello ''world''\n",
+ [ LinksDeletionUpdate::class => [] ]
+ ],
+ [ "WikitextContentTest_testGetSecondaryDataUpdates_2",
+ CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n",
+ [ LinksDeletionUpdate::class => [] ]
+ ],
+ // @todo more...?
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/content/WikitextStructureTest.php b/www/wiki/tests/phpunit/includes/content/WikitextStructureTest.php
new file mode 100644
index 00000000..88f4d8f7
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/content/WikitextStructureTest.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @covers WikiTextStructure
+ */
+class WikitextStructureTest extends MediaWikiLangTestCase {
+
+ private function getMockTitle() {
+ return Title::newFromText( "TestTitle" );
+ }
+
+ /**
+ * Get parser output for Wiki text
+ * @param string $text
+ * @return ParserOutput
+ */
+ private function getParserOutput( $text ) {
+ $content = new WikitextContent( $text );
+ return $content->getParserOutput( $this->getMockTitle() );
+ }
+
+ /**
+ * Get WikitextStructure for given text
+ * @param string $text
+ * @return WikiTextStructure
+ */
+ private function getStructure( $text ) {
+ return new WikiTextStructure( $this->getParserOutput( $text ) );
+ }
+
+ public function testHeadings() {
+ $text = <<<END
+Some text here
+== Heading one ==
+Some text
+==== heading two ====
+More text
+=== Applicability of the strict mass-energy equivalence formula, ''E'' = ''mc''<sup>2</sup> ===
+and more text
+== Wikitext '''in''' [[Heading]] and also <b>html</b> ==
+more text
+==== See also ====
+* Also things to see!
+END;
+ $struct = $this->getStructure( $text );
+ $headings = $struct->headings();
+ $this->assertCount( 4, $headings );
+ $this->assertContains( "Heading one", $headings );
+ $this->assertContains( "heading two", $headings );
+ $this->assertContains( "Applicability of the strict mass-energy equivalence formula, E = mc2",
+ $headings );
+ $this->assertContains( "Wikitext in Heading and also html", $headings );
+ }
+
+ public function testDefaultSort() {
+ $text = <<<END
+Louise Michel
+== Heading one ==
+Some text
+==== See also ====
+* Also things to see!
+{{DEFAULTSORT:Michel, Louise}}
+END;
+ $struct = $this->getStructure( $text );
+ $this->assertEquals( "Michel, Louise", $struct->getDefaultSort() );
+ }
+
+ public function testHeadingsFirst() {
+ $text = <<<END
+== Heading one ==
+Some text
+==== heading two ====
+END;
+ $struct = $this->getStructure( $text );
+ $headings = $struct->headings();
+ $this->assertCount( 2, $headings );
+ $this->assertContains( "Heading one", $headings );
+ $this->assertContains( "heading two", $headings );
+ }
+
+ public function testHeadingsNone() {
+ $text = "This text is completely devoid of headings.";
+ $struct = $this->getStructure( $text );
+ $headings = $struct->headings();
+ $this->assertArrayEquals( [], $headings );
+ }
+
+ public function testTexts() {
+ $text = <<<END
+Opening text is opening.
+== Then comes header ==
+Then we got more<br>text
+=== And more headers ===
+{| class="wikitable"
+|-
+! Header table
+|-
+| row in table
+|-
+| another row in table
+|}
+END;
+ $struct = $this->getStructure( $text );
+ $this->assertEquals( "Opening text is opening.", $struct->getOpeningText() );
+ $this->assertEquals( "Opening text is opening. Then we got more text",
+ $struct->getMainText() );
+ $this->assertEquals( [ "Header table row in table another row in table" ],
+ $struct->getAuxiliaryText() );
+ }
+}