Use Title, not IContextSource; remove createArticle, etc.
authordaniel <daniel.kinzler@wikimedia.de>
Wed, 23 May 2012 06:53:01 +0000 (08:53 +0200)
committerdaniel <daniel.kinzler@wikimedia.de>
Wed, 23 May 2012 06:53:01 +0000 (08:53 +0200)
This merges the latest core patch into the Wikidata branch,
implementing suggestions collected on gerrit. Most importantly:

* Methods in the Content class no longer rely on a IContextSource
* createArticle and createEditPage were removed from Contenthandler

14 files changed:
includes/Article.php
includes/Content.php
includes/ContentHandler.php
includes/EditPage.php
includes/LinksUpdate.php
includes/Title.php
includes/WikiFilePage.php
includes/WikiPage.php
includes/actions/EditAction.php
maintenance/refreshLinks.php
tests/phpunit/includes/ContentHandlerTest.php
tests/phpunit/includes/WikiPageTest.php
tests/phpunit/includes/WikitextContentTest.php
tests/phpunit/includes/WikitextContentTest.php.orig [new file with mode: 0644]

index defc9e1..57ccab2 100644 (file)
@@ -119,14 +119,13 @@ class Article extends Page {
                if ( !$page ) {
                        switch( $title->getNamespace() ) {
                                case NS_FILE:
-                                       $page = new ImagePage( $title ); #FIXME: teach ImagePage to use ContentHandler
+                                       $page = new ImagePage( $title );
                                        break;
                                case NS_CATEGORY:
-                                       $page = new CategoryPage( $title ); #FIXME: teach ImagePage to use ContentHandler
+                                       $page = new CategoryPage( $title );
                                        break;
                                default:
-                                       $handler = ContentHandler::getForTitle( $title );
-                                       $page = $handler->createArticle( $title );
+                                       $page = new Article( $title );
                        }
                }
                $page->setContext( $context );
@@ -642,7 +641,7 @@ class Article extends Page {
                                                        # Viewing a redirect page (e.g. with parameter redirect=no)
                                                        $outputPage->addHTML( $this->viewRedirect( $rt ) );
                                                        # Parse just to get categories, displaytitle, etc.
-                                                       $this->mParserOutput = $content->getParserOutput( $this->getContext(), $oldid, $parserOptions, false );
+                                                       $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions, false );
                                                        $outputPage->addParserOutputNoText( $this->mParserOutput );
                                                        $outputDone = true;
                                                }
@@ -780,7 +779,7 @@ class Article extends Page {
 
                // Give hooks a chance to customise the output
                if ( !Hooks::isRegistered('ShowRawCssJs') || wfRunHooks( 'ShowRawCssJs', array( $this->fetchContent(), $this->getTitle(), $wgOut ) ) ) { #FIXME: fetchContent() is deprecated
-                       $po = $this->mContentObject->getParserOutput( $this->getContext() );
+                       $po = $this->mContentObject->getParserOutput( $this->getTitle() );
                        $wgOut->addHTML( $po->getText() );
                }
        }
index 5402499..9463c91 100644 (file)
@@ -291,7 +291,7 @@ abstract class Content {
        public abstract function isCountable( $hasLinks = null ) ;
 
        /**
-        * @param IContextSource $context
+        * @param Title $title
         * @param null $revId
         * @param null|ParserOptions $options
         * @param Boolean $generateHtml whether to generate Html (default: true). If false,
@@ -302,20 +302,20 @@ abstract class Content {
         *
         * @return ParserOutput
         */
-       public abstract function getParserOutput( IContextSource $context, $revId = null, ParserOptions $options = null, $generateHtml = true );
+       public abstract function getParserOutput( Title $title, $revId = null, ParserOptions $options = null, $generateHtml = true );
 
        /**
         * Returns a list of DataUpdate objects for recording information about this Content in some secondary
         * data store. If the optional second argument, $old, is given, the updates may model only the changes that
         * need to be made to replace information about the old content with infomration about the new content.
         *
-        * This default implementation calls $this->getParserOutput( $context, null, null, false ), and then
-        * calls getSecondaryDataUpdates( $context->getTitle(), $recursive ) on the resulting ParserOutput object.
+        * This default implementation calls $this->getParserOutput( $title, null, null, false ), and then
+        * calls getSecondaryDataUpdates( $title, $recursive ) on the resulting ParserOutput object.
         *
         * Subclasses may implement this to determine the necessary updates more efficiently, or make use of information
         * about the old content.
         *
-        * @param IContextSource $context the content for determining the necessary updates
+        * @param Title $title the context for determining the necessary updates
         * @param Content|null $old a Content object representing the previous content, i.e. the content being
         *                     replaced by this Content object.
         * @param bool $recursive whether to include recursive updates (default: false).
@@ -324,9 +324,9 @@ abstract class Content {
         *
         * @since WD.1
         */
-       public function getSecondaryDataUpdates( IContextSource $context, Content $old = null, $recursive = false ) {
-               $po = $this->getParserOutput( $context, null, null, false );
-               return $po->getSecondaryDataUpdates( $context->getTitle(), $recursive );
+       public function getSecondaryDataUpdates( Title $title, Content $old = null, $recursive = false ) {
+               $po = $this->getParserOutput( $title, null, null, false );
+               return $po->getSecondaryDataUpdates( $title, $recursive );
        }
 
        /**
@@ -563,7 +563,7 @@ abstract class TextContent extends Content {
         *
         * @return ParserOutput representing the HTML form of the text
         */
-       public function getParserOutput( IContextSource $context, $revId = null, ParserOptions $options = null, $generateHtml = true ) {
+       public function getParserOutput( Title $title, $revId = null, ParserOptions $options = null, $generateHtml = true ) {
                # generic implementation, relying on $this->getHtml()
 
                if ( $generateHtml ) $html = $this->getHtml( $options );
@@ -603,14 +603,14 @@ class WikitextContent extends TextContent {
         *
         * @return ParserOutput representing the HTML form of the text
         */
-       public function getParserOutput( IContextSource $context, $revId = null, ParserOptions $options = null, $generateHtml = true ) {
+       public function getParserOutput( Title $title, $revId = null, ParserOptions $options = null, $generateHtml = true ) {
                global $wgParser;
 
                if ( !$options ) {
-                       $options = ParserOptions::newFromUserAndLang( $context->getUser(), $context->getLanguage() );
+                       $options = new ParserOptions();
                }
 
-               $po = $wgParser->parse( $this->mText, $context->getTitle(), $options, true, true, $revId );
+               $po = $wgParser->parse( $this->mText, $title, $options, true, true, $revId );
 
                return $po;
        }
@@ -748,7 +748,7 @@ class WikitextContent extends TextContent {
         *
         * @return bool true if the content is countable
         */
-       public function isCountable( $hasLinks = null, IContextSource $context = null ) {
+       public function isCountable( $hasLinks = null, Title $title = null ) {
                global $wgArticleCountMethod, $wgRequest;
 
                if ( $this->isRedirect( ) ) {
@@ -764,12 +764,12 @@ class WikitextContent extends TextContent {
                                return strpos( $text,  ',' ) !== false;
                        case 'link':
                                if ( $hasLinks === null ) { # not known, find out
-                                       if ( !$context ) { # make dummy context
-                                               //XXX: caller of this method often knows the title, but not a context...
-                                               $context = new RequestContext( $wgRequest );
+                                       if ( !$title ) {
+                                               $context = RequestContext::getMain();
+                                               $title = $context->getTitle();
                                        }
 
-                                       $po = $this->getParserOutput( $context, null, null, false );
+                                       $po = $this->getParserOutput( $title, null, null, false );
                                        $links = $po->getLinks();
                                        $hasLinks = !empty( $links );
                                }
index ba33a01..80f729b 100644 (file)
@@ -475,56 +475,6 @@ abstract class ContentHandler {
                return array();
        }
 
-       /**
-        * Return an Article object suitable for viewing the given object
-        *
-        * NOTE: does *not* do special handling for Image and Category pages!
-        *       Use Article::newFromTitle() for that!
-        *
-        * @since WD.1
-        *
-        * @param Title $title
-        * @return Article
-        * @todo Article is being refactored into an action class, keep track of that
-        * @todo Article really defines the view of the content... rename this method to createViewPage ?
-        */
-       public function createArticle( Title $title ) {
-               $this->checkModelID( $title->getContentModel() );
-
-               $article = new Article($title);
-               return $article;
-       }
-
-       /**
-        * Return an EditPage object suitable for editing the given object.
-        * This default  implementation always fails with an MWException, because there is no
-        * generic edit page implementation suitable for all content models.
-        *
-        * @since WD.1
-        *
-        * @param Article $article
-        * @return EditPage
-        */
-       public function createEditPage( Article $article ) {
-               throw new MWException( "ContentHandler class " . get_classs( $this ) . " does not provide an EditPage." );
-       }
-
-       /**
-        * Return an ExternalEdit object suitable for editing the given object
-        *
-        * @since WD.1
-        *
-        * @param IContextSource $context
-        * @return ExternalEdit
-        * @todo does anyone or anythign actually use the external edit facility? Can we just deprecate and ignore it?
-        */
-       public function createExternalEdit( IContextSource $context ) {
-               $this->checkModelID( $context->getTitle()->getContentModel() );
-
-               $externalEdit = new ExternalEdit( $context );
-               return $externalEdit;
-       }
-
        /**
         * Factory
         * @since WD.1
@@ -816,21 +766,6 @@ abstract class TextContentHandler extends ContentHandler {
                return $content->getNativeData();
        }
 
-       /**
-        * Return an EditPage object for editing the given object
-        *
-        * @since WD.1
-        *
-        * @param Article $article
-        * @return EditPage
-        */
-       public function createEditPage( Article $article ) {
-               $this->checkModelID( $article->getPage()->getContentModel() );
-
-               $editPage = new EditPage( $article );
-               return $editPage;
-       }
-
        /**
         * attempts to merge differences between three versions.
         * Returns a new Content object for a clean merge and false for failure or a conflict.
index 72341b4..5273c54 100644 (file)
@@ -2821,7 +2821,7 @@ HTML
                                $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
 
                                // TODO: might be a saner way to get a meaningfull context here?
-                               $parserOutput = $content->getParserOutput( $this->getArticle()->getContext(), null, $parserOptions );
+                               $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
 
                                $previewHTML = $parserOutput->getText();
                                $this->mParserOutput = $parserOutput;
index 56595ca..4b484a7 100644 (file)
@@ -63,6 +63,7 @@ class LinksUpdate extends SqlDataUpdate {
 
                $this->mTitle = $title;
                $this->mId = $title->getArticleID();
+               assert( $this->mId > 0 );
 
                $this->mParserOutput = $parserOutput;
 
index 880c95f..1681c4a 100644 (file)
@@ -2857,8 +2857,12 @@ class Title {
                if ( !$this->getArticleID( $flags ) ) {
                        return $this->mRedirect = false;
                }
+
                $linkCache = LinkCache::singleton();
-               $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+               $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
+               assert( $cached !== null ); # assert the assumption that the cache actually knows about this title
+
+               $this->mRedirect = (bool)$cached;
 
                return $this->mRedirect;
        }
index 9fb1522..3197b1d 100644 (file)
@@ -41,7 +41,9 @@ class WikiFilePage extends WikiPage {
        }
 
        public function getActionOverrides() {
-               return array( 'revert' => 'RevertFileAction' );
+               $overrides = parent::getActionOverrides();
+               $overrides[ 'revert' ] = 'RevertFileAction';
+               return $overrides;
        }
 
        /**
index 8f73eec..dae465e 100644 (file)
@@ -1093,11 +1093,10 @@ class WikiPage extends Page {
         * @param $parserOptions ParserOptions to use for the parse operation
         * @param $oldid Revision ID to get the text from, passing null or 0 will
         *               get the current revision (default value)
-        * @param $context IContextSource context for parsing
         *
         * @return ParserOutput or false if the revision was not found
         */
-       public function getParserOutput( ParserOptions $parserOptions, $oldid = null, IContextSource $context = null ) {
+       public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
                wfProfileIn( __METHOD__ );
 
                $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
@@ -1118,7 +1117,7 @@ class WikiPage extends Page {
                        $oldid = $this->getLatest();
                }
 
-               $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache, null, $context );
+               $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache, null );
                $pool->execute();
 
                wfProfileOut( __METHOD__ );
@@ -1934,11 +1933,7 @@ class WikiPage extends Page {
 
                $edit->popts = $this->makeParserOptions( 'canonical' );
 
-               // TODO: is there no better way to obtain a context here?
-               $context = RequestContext::getMain();
-               $context->setTitle( $this->mTitle );
-               $edit->output = $edit->pstContent->getParserOutput( $context, $revid, $edit->popts );
-               $edit->updates = $edit->pstContent->getSecondaryDataUpdate( $context );
+               $edit->output = $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts );
 
                $edit->newContent = $content;
                $edit->oldContent = $this->getContent( Revision::RAW );
@@ -1992,7 +1987,7 @@ class WikiPage extends Page {
                }
 
                # Update the links tables and other secondary data
-               $updates = $editInfo->updates;
+               $updates = $editInfo->output->getSecondaryDataUpdates( $this->getTitle() );
                DataUpdate::runUpdates( $updates );
 
                wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
@@ -3225,9 +3220,9 @@ class PoolWorkArticleView extends PoolCounterWork {
        private $parserOptions;
 
        /**
-        * @var string|null
+        * @var Content|null
         */
-       private $text;
+       private $content = null;
 
        /**
         * @var ParserOutput|bool
@@ -3252,23 +3247,16 @@ class PoolWorkArticleView extends PoolCounterWork {
         * @param $useParserCache Boolean: whether to use the parser cache
         * @param $parserOptions parserOptions to use for the parse operation
         * @param $content Content|String: content to parse or null to load it; may also be given as a wikitext string, for BC
-        * @param $context IContextSource context for parsing
         */
-       function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null, IContextSource $context = null ) {
+       function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null ) {
                if ( is_string($content) ) { #BC: old style call
                        $modelId = $page->getRevision()->getContentModel();
                        $format = $page->getRevision()->getContentFormat();
                        $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
                }
 
-               if ( is_null( $context ) ) {
-                       $context = RequestContext::getMain();
-                       #XXX: clone and then set title?
-               }
-
                $this->page = $page;
                $this->revid = $revid;
-               $this->context = $context;
                $this->cacheable = $useParserCache;
                $this->parserOptions = $parserOptions;
                $this->content = $content;
@@ -3327,7 +3315,7 @@ class PoolWorkArticleView extends PoolCounterWork {
 
                $time = - microtime( true );
                // TODO: page might not have this method? Hard to tell what page is supposed to be here...
-               $this->parserOutput = $content->getParserOutput( $this->context, $this->revid, $this->parserOptions );
+               $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
                $time += microtime( true );
 
                # Timing hack
index 2888d24..3771586 100644 (file)
@@ -40,7 +40,10 @@ class EditAction extends FormlessAction {
                $context = $this->getContext();
 
                if ( wfRunHooks( 'CustomEditor', array( $page, $user ) ) ) {
-            $handler = ContentHandler::getForTitle( $page->getTitle() );
+                       if ( ( $page->getContent() instanceof TextContentHandler ) ) {
+                               $modelName = ContentHandler::getContentModelName( $page->getContentModel() );
+                               throw new MWException( "Can't use default editor for non-text content. ContentHandler for $modelName apparently does not provide an action handler for the edit action." );
+                       }
 
                        if ( ExternalEdit::useExternalEngine( $context, 'edit' )
                                && $this->getName() == 'edit' && !$request->getVal( 'section' )
index f954b9e..602874b 100644 (file)
@@ -220,7 +220,7 @@ class RefreshLinks extends Maintenance {
 
                $context = RequestContext::getMain();
 
-               $updates = $content->getSecondaryDataUpdates( $context );
+               $updates = $parserOutput->getSecondaryDataUpdates( $page->getTitle(), false );
                DataUpdate::runUpdates( $updates );
 
                $dbw->commit( __METHOD__ );
index 8465c20..05cfb67 100644 (file)
@@ -383,7 +383,7 @@ class DummyContentForTesting extends Content {
        }
 
        /**
-        * @param IContextSource $context
+        * @param Title $title
         * @param null $revId
         * @param null|ParserOptions $options
         * @param Boolean $generateHtml whether to generate Html (default: true). If false,
@@ -392,7 +392,7 @@ class DummyContentForTesting extends Content {
         *
         * @return ParserOutput
         */
-       public function getParserOutput( IContextSource $context, $revId = null, ParserOptions $options = NULL, $generateHtml = true )
+       public function getParserOutput( Title $title, $revId = null, ParserOptions $options = NULL, $generateHtml = true )
        {
                return new ParserOutput( $this->data );
        }
index 2540603..cdf6fcd 100644 (file)
@@ -32,6 +32,8 @@ class WikiPageTest extends MediaWikiTestCase {
        
        public function setUp() {
                $this->pages_to_delete = array();
+
+               LinkCache::singleton()->clear(); # avoid cached redirect status, etc
        }
 
        public function tearDown() {
@@ -48,6 +50,10 @@ class WikiPageTest extends MediaWikiTestCase {
                }
        }
 
+       /**
+        * @param Title $title
+        * @return WikiPage
+        */
        protected function newPage( $title ) {
                if ( is_string( $title ) ) $title = Title::newFromText( $title );
 
@@ -58,9 +64,23 @@ class WikiPageTest extends MediaWikiTestCase {
                return $p;
        }
 
+
+       /**
+        * @param String|Title|WikiPage $page
+        * @param String $text
+        * @param int $model
+        *
+        * @return WikiPage
+        */
        protected function createPage( $page, $text, $model = null ) {
                if ( is_string( $page ) ) $page = Title::newFromText( $page );
-               if ( $page instanceof Title ) $page = $this->newPage( $page );
+
+               if ( $page instanceof Title ) {
+                       $title = $page;
+                       $page = $this->newPage( $page );
+               } else {
+                       $title = null;
+               }
 
                $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
                $page->doEditContent( $content, "testing", EDIT_NEW );
@@ -77,13 +97,23 @@ class WikiPageTest extends MediaWikiTestCase {
                                                        . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
                                                        $title );
 
-               $page->doEditContent( $content, "testing 1" );
+               $page->doEditContent( $content, "[[testing]] 1" );
 
+               $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" );
+               $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" );
                $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
                $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
 
                $id = $page->getId();
 
+               # ------------------------
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) );
+               $n = $res->numRows();
+               $res->free();
+
+               $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
+
                # ------------------------
                $page = new WikiPage( $title );
 
@@ -120,13 +150,23 @@ class WikiPageTest extends MediaWikiTestCase {
                $text = "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
                       . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.";
 
-               $page->doEdit( $text, "testing 1" );
+               $page->doEdit( $text, "[[testing]] 1" );
 
+               $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" );
+               $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" );
                $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
                $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
 
                $id = $page->getId();
 
+               # ------------------------
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) );
+               $n = $res->numRows();
+               $res->free();
+
+               $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
+
                # ------------------------
                $page = new WikiPage( $title );
 
@@ -186,6 +226,8 @@ class WikiPageTest extends MediaWikiTestCase {
 
                $page->doDeleteArticle( "testing deletion" );
 
+               $this->assertFalse( $page->getTitle()->getArticleID() > 0, "Title object should now have page id 0" );
+               $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" );
                $this->assertFalse( $page->exists(), "WikiPage::exists should return false after page was deleted" );
                $this->assertNull( $page->getContent(), "WikiPage::getContent should return null after page was deleted" );
                $this->assertFalse( $page->getText(), "WikiPage::getText should return false after page was deleted" );
@@ -444,15 +486,19 @@ class WikiPageTest extends MediaWikiTestCase {
        public function testIsCountable( $title, $text, $mode, $expected ) {
                global $wgArticleCountMethod;
 
-               $old = $wgArticleCountMethod;
+               $oldArticleCountMethod = $wgArticleCountMethod;
                $wgArticleCountMethod = $mode;
 
                $page = $this->createPage( $title, $text );
+               $hasLinks = wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+                                       array( 'pl_from' => $page->getId() ), __METHOD__ );
+
                $editInfo = $page->prepareContentForEdit( $page->getContent() );
 
                $v = $page->isCountable();
                $w = $page->isCountable( $editInfo );
-               $wgArticleCountMethod = $old;
+
+               $wgArticleCountMethod = $oldArticleCountMethod;
 
                $this->assertEquals( $expected, $v, "isCountable( null ) returned unexpected value " . var_export( $v, true )
                                                    . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" );
index 57f992a..5feec83 100644 (file)
@@ -27,7 +27,7 @@ class WikitextContentTest extends MediaWikiTestCase {
        public function testGetParserOutput( $text, $expectedHtml ) {
                $content = $this->newContent( $text );
 
-               $po = $content->getParserOutput( $this->context );
+               $po = $content->getParserOutput( $this->context->getTitle() );
 
                $this->assertEquals( $expectedHtml, $po->getText() );
                return $po;
@@ -304,7 +304,7 @@ just a test"
 
                $content = $this->newContent( $text );
 
-               $v = $content->isCountable( $hasLinks, $this->context );
+               $v = $content->isCountable( $hasLinks, $this->context->getTitle() );
                $wgArticleCountMethod = $old;
 
                $this->assertEquals( $expected, $v, "isCountable() returned unexpected value " . var_export( $v, true )
diff --git a/tests/phpunit/includes/WikitextContentTest.php.orig b/tests/phpunit/includes/WikitextContentTest.php.orig
new file mode 100644 (file)
index 0000000..b0e9c8d
--- /dev/null
@@ -0,0 +1,438 @@
+<?php
+
+/**
+ * @group ContentHandler
+ */
+class WikitextContentTest extends MediaWikiTestCase {
+
+       public function setup() {
+               $this->context = new RequestContext( new FauxRequest() );
+               $this->context->setTitle( Title::newFromText( "Test" ) );
+       }
+
+       public function newContent( $text ) {
+               return new WikitextContent( $text );
+       }
+
+       public function dataGetParserOutput() {
+               return array(
+                       array("hello ''world''\n", "<p>hello <i>world</i>\n</p>"),
+                       // @todo: more...?
+               );
+       }
+
+       /**
+        * @dataProvider dataGetParserOutput
+        */
+       public function testGetParserOutput( $text, $expectedHtml ) {
+               $content = $this->newContent( $text );
+
+               $po = $content->getParserOutput( $this->context );
+
+               $this->assertEquals( $expectedHtml, $po->getText() );
+       }
+
+       public function dataGetSecondaryDataUpdates() {
+               return array(
+                       // @todo: more...?
+               );
+       }
+
+       /**
+        * @dataProvider dataGetParserOutput
+        */
+       public function testGetSecondaryDataUpdates( $text, $expectedLinks ) {
+               $content = $this->newContent( "hello [[world]]\n" );
+
+               $updates = $content->getSecondaryDataUpdates( $this->context );
+
+               $this->assertEquals( 1, count( $updates ) );
+               $this->assertEquals( "LinksUpdate", get_class( $updates[0] ) );
+       }
+
+       static $sections =
+
+"Intro
+
+== stuff ==
+hello world
+
+== test ==
+just a test
+
+== foo ==
+more stuff
+";
+
+       public function dataGetSection() {
+               return array(
+                       array( WikitextContentTest::$sections,
+                                       "0",
+                                       "Intro"
+                       ),
+                       array( WikitextContentTest::$sections,
+                                       "2",
+"== test ==
+just a test"
+                       ),
+                       array( WikitextContentTest::$sections,
+                                       "8",
+                                       false
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataGetSection
+        */
+       public function testGetSection( $text, $sectionId, $expectedText ) {
+               $content = $this->newContent( $text );
+
+               $sectionContent = $content->getSection( $sectionId );
+
+               $this->assertEquals( $expectedText, is_null( $sectionContent ) ? null : $sectionContent->getNativeData() );
+       }
+
+       public function dataReplaceSection() {
+               return array(
+                       array( WikitextContentTest::$sections,
+                              "0",
+                              "No more",
+                              null,
+                              trim( preg_replace( '/^Intro/sm', 'No more', WikitextContentTest::$sections ) )
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "",
+                              "No more",
+                              null,
+                              "No more"
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "2",
+                              "== TEST ==\nmore fun",
+                              null,
+                              trim( preg_replace( '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==", WikitextContentTest::$sections ) )
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "8",
+                              "No more",
+                              null,
+                              WikitextContentTest::$sections
+                       ),
+                       array( WikitextContentTest::$sections,
+                              "new",
+                              "No more",
+                              "New",
+                              trim( WikitextContentTest::$sections ) . "\n\n\n== New ==\n\nNo more"
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataReplaceSection
+        */
+       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() );
+       }
+
+       public function testAddSectionHeader( ) {
+               $content = $this->newContent( 'hello world' );
+               $content = $content->addSectionHeader( 'test' );
+
+               $this->assertEquals( "== test ==\n\nhello world", $content->getNativeData() );
+       }
+
+       public function dataPreSaveTransform() {
+               return array(
+                       array( 'hello this is ~~~',
+                              "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
+                       ),
+                       array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+                              'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataPreSaveTransform
+        */
+       public function testPreSaveTransform( $text, $expected ) {
+               global $wgUser, $wgContLang;
+               $options = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
+
+               $content = $this->newContent( $text );
+               $content = $content->preSaveTransform( $this->context->getTitle(), $this->context->getUser(), $options );
+
+               $this->assertEquals( $expected, $content->getNativeData() );
+       }
+
+       public function dataPreloadTransform() {
+               return array(
+                       array( 'hello this is ~~~',
+                              "hello this is ~~~",
+                       ),
+                       array( 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
+                              'hello \'\'this\'\' is bar',
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataPreloadTransform
+        */
+       public function testPreloadTransform( $text, $expected ) {
+               global $wgUser, $wgContLang;
+               $options = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
+
+               $content = $this->newContent( $text );
+               $content = $content->preloadTransform( $this->context->getTitle(), $options );
+
+               $this->assertEquals( $expected, $content->getNativeData() );
+       }
+
+       public function dataGetRedirectTarget() {
+               return array(
+                       array( '#REDIRECT [[Test]]',
+                              'Test',
+                       ),
+                       array( '#REDIRECT Test',
+                              null,
+                       ),
+                       array( '* #REDIRECT [[Test]]',
+                              null,
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataGetRedirectTarget
+        */
+       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
+        */
+       public function isRedirect( $text, $expected ) {
+               $content = $this->newContent( $text );
+
+               $this->assertEquals( !is_null($expected), $content->isRedirect() );
+       }
+
+
+       /**
+        * @todo: test needs database!
+        */
+       /*
+       public function getRedirectChain() {
+               $text = $this->getNativeData();
+               return Title::newFromRedirectArray( $text );
+       }
+       */
+
+       /**
+        * @todo: test needs database!
+        */
+       /*
+       public function getUltimateRedirectTarget() {
+               $text = $this->getNativeData();
+               return Title::newFromRedirectRecurse( $text );
+       }
+       */
+
+
+       public function dataIsCountable() {
+               return array(
+                       array( '',
+                              null,
+                              'any',
+                              true
+                       ),
+                       array( 'Foo',
+                              null,
+                              'any',
+                              true
+                       ),
+                       array( 'Foo',
+                              null,
+                              'comma',
+                              false
+                       ),
+                       array( 'Foo, bar',
+                              null,
+                              'comma',
+                              true
+                       ),
+                       array( 'Foo',
+                              null,
+                              'link',
+                              false
+                       ),
+                       array( 'Foo [[bar]]',
+                              null,
+                              'link',
+                              true
+                       ),
+                       array( 'Foo',
+                              true,
+                              'link',
+                              true
+                       ),
+                       array( 'Foo [[bar]]',
+                              false,
+                              'link',
+                              false
+                       ),
+                       array( '#REDIRECT [[bar]]',
+                              true,
+                              'any',
+                              false
+                       ),
+                       array( '#REDIRECT [[bar]]',
+                              true,
+                              'comma',
+                              false
+                       ),
+                       array( '#REDIRECT [[bar]]',
+                              true,
+                              'link',
+                              false
+                       ),
+               );
+       }
+
+
+       /**
+        * @dataProvider dataIsCountable
+        */
+       public function testIsCountable( $text, $hasLinks, $mode, $expected ) {
+               global $wgArticleCountMethod;
+
+               $old = $wgArticleCountMethod;
+               $wgArticleCountMethod = $mode;
+
+               $content = $this->newContent( $text );
+
+               $v = $content->isCountable( $hasLinks, $this->context );
+               $wgArticleCountMethod = $old;
+
+               $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 function dataGetTextForSummary() {
+               return array(
+                       array( "hello\nworld.",
+                              16,
+                              'hello world.',
+                       ),
+                       array( 'hello world.',
+                              8,
+                              'hello...',
+                       ),
+                       array( '[[hello world]].',
+                              8,
+                              'hel...',
+                       ),
+               );
+       }
+
+       /**
+        * @dataProvider dataGetTextForSummary
+        */
+       public function testGetTextForSummary( $text, $maxlength, $expected ) {
+               $content = $this->newContent( $text );
+
+               $this->assertEquals( $expected, $content->getTextForSummary( $maxlength ) );
+       }
+
+
+       public function testGetTextForSearchIndex( ) {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( "hello world.", $content->getTextForSearchIndex() );
+       }
+
+       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() );
+       }
+
+       public function testGetSize( ) {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( 12, $content->getSize() );
+       }
+
+       public function testGetNativeData( ) {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( "hello world.", $content->getNativeData() );
+       }
+
+       public function testGetWikitextForTransclusion( ) {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( "hello world.", $content->getWikitextForTransclusion() );
+       }
+
+       # =================================================================================================================
+
+       public function testGetModel() {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getModel() );
+       }
+
+       public function testGetContentHandler() {
+               $content = $this->newContent( "hello world." );
+
+               $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getContentHandler()->getModelID() );
+       }
+
+       public function dataIsEmpty( ) {
+               return array(
+                       array( '', true ),
+                       array( '  ', false ),
+                       array( '0', false ),
+                       array( 'hallo welt.', false ),
+               );
+       }
+
+       /**
+        * @dataProvider dataIsEmpty
+        */
+       public function testIsEmpty( $text, $empty ) {
+               $content = $this->newContent( $text );
+
+               $this->assertEquals( $empty, $content->isEmpty() );
+       }
+
+       public function dataEquals( ) {
+               return array(
+                       array( new WikitextContent( "hallo" ), null, false ),
+                       array( new WikitextContent( "hallo" ), new WikitextContent( "hallo" ), true ),
+                       array( new WikitextContent( "hallo" ), new JavascriptContent( "hallo" ), false ),
+                       array( new WikitextContent( "hallo" ), new WikitextContent( "HALLO" ), false ),
+               );
+       }
+
+       /**
+        * @dataProvider dataEquals
+        */
+       public function testEquals( Content $a, Content $b = null, $equal = false ) {
+               $this->assertEquals( $equal, $a->equals( $b ) );
+       }
+
+}