2 use MediaWiki\MediaWikiServices
;
5 * @group ContentHandler
8 class ContentHandlerTest
extends MediaWikiTestCase
{
10 protected function setUp() {
14 $this->setMwGlobals( [
15 'wgExtraNamespaces' => [
17 12313 => 'Dummy_talk',
19 // The below tests assume that namespaces not mentioned here (Help, User, MediaWiki, ..)
20 // default to CONTENT_MODEL_WIKITEXT.
21 'wgNamespaceContentModels' => [
24 'wgContentHandlers' => [
25 CONTENT_MODEL_WIKITEXT
=> WikitextContentHandler
::class,
26 CONTENT_MODEL_JAVASCRIPT
=> JavaScriptContentHandler
::class,
27 CONTENT_MODEL_JSON
=> JsonContentHandler
::class,
28 CONTENT_MODEL_CSS
=> CssContentHandler
::class,
29 CONTENT_MODEL_TEXT
=> TextContentHandler
::class,
30 'testing' => DummyContentHandlerForTesting
::class,
31 'testing-callbacks' => function ( $modelId ) {
32 return new DummyContentHandlerForTesting( $modelId );
37 // Reset namespace cache
38 MWNamespace
::clearCaches();
39 $wgContLang->resetNamespaces();
41 MediaWikiServices
::getInstance()->resetServiceForTesting( 'LinkCache' );
44 protected function tearDown() {
47 // Reset namespace cache
48 MWNamespace
::clearCaches();
49 $wgContLang->resetNamespaces();
51 MediaWikiServices
::getInstance()->resetServiceForTesting( 'LinkCache' );
56 public function addDBDataOnce() {
57 $this->insertPage( 'Not_Main_Page', 'This is not a main page' );
58 $this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]' );
61 public static function dataGetDefaultModelFor() {
63 [ 'Help:Foo', CONTENT_MODEL_WIKITEXT
],
64 [ 'Help:Foo.js', CONTENT_MODEL_WIKITEXT
],
65 [ 'Help:Foo.css', CONTENT_MODEL_WIKITEXT
],
66 [ 'Help:Foo.json', CONTENT_MODEL_WIKITEXT
],
67 [ 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT
],
68 [ 'User:Foo', CONTENT_MODEL_WIKITEXT
],
69 [ 'User:Foo.js', CONTENT_MODEL_WIKITEXT
],
70 [ 'User:Foo.css', CONTENT_MODEL_WIKITEXT
],
71 [ 'User:Foo.json', CONTENT_MODEL_WIKITEXT
],
72 [ 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT
],
73 [ 'User:Foo/bar.css', CONTENT_MODEL_CSS
],
74 [ 'User:Foo/bar.json', CONTENT_MODEL_JSON
],
75 [ 'User:Foo/bar.json.nope', CONTENT_MODEL_WIKITEXT
],
76 [ 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT
],
77 [ 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT
],
78 [ 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT
],
79 [ 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT
],
80 [ 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT
],
81 [ 'MediaWiki:Foo.css', CONTENT_MODEL_CSS
],
82 [ 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT
],
83 [ 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT
],
84 [ 'MediaWiki:Foo.json', CONTENT_MODEL_JSON
],
85 [ 'MediaWiki:Foo.JSON', CONTENT_MODEL_WIKITEXT
],
90 * @dataProvider dataGetDefaultModelFor
91 * @covers ContentHandler::getDefaultModelFor
93 public function testGetDefaultModelFor( $title, $expectedModelId ) {
94 $title = Title
::newFromText( $title );
95 $this->assertEquals( $expectedModelId, ContentHandler
::getDefaultModelFor( $title ) );
99 * @dataProvider dataGetDefaultModelFor
100 * @covers ContentHandler::getForTitle
102 public function testGetForTitle( $title, $expectedContentModel ) {
103 $title = Title
::newFromText( $title );
104 LinkCache
::singleton()->addBadLinkObj( $title );
105 $handler = ContentHandler
::getForTitle( $title );
106 $this->assertEquals( $expectedContentModel, $handler->getModelID() );
109 public static function dataGetLocalizedName() {
114 // XXX: depends on content language
115 [ CONTENT_MODEL_JAVASCRIPT
, '/javascript/i' ],
120 * @dataProvider dataGetLocalizedName
121 * @covers ContentHandler::getLocalizedName
123 public function testGetLocalizedName( $id, $expected ) {
124 $name = ContentHandler
::getLocalizedName( $id );
127 $this->assertNotNull( $name, "no name found for content model $id" );
128 $this->assertTrue( preg_match( $expected, $name ) > 0,
129 "content model name for #$id did not match pattern $expected"
132 $this->assertEquals( $id, $name, "localization of unknown model $id should have "
133 . "fallen back to use the model id directly."
138 public static function dataGetPageLanguage() {
139 global $wgLanguageCode;
142 [ "Main", $wgLanguageCode ],
143 [ "Dummy:Foo", $wgLanguageCode ],
144 [ "MediaWiki:common.js", 'en' ],
145 [ "User:Foo/common.js", 'en' ],
146 [ "MediaWiki:common.css", 'en' ],
147 [ "User:Foo/common.css", 'en' ],
148 [ "User:Foo", $wgLanguageCode ],
150 [ CONTENT_MODEL_JAVASCRIPT
, 'javascript' ],
155 * @dataProvider dataGetPageLanguage
156 * @covers ContentHandler::getPageLanguage
158 public function testGetPageLanguage( $title, $expected ) {
159 if ( is_string( $title ) ) {
160 $title = Title
::newFromText( $title );
161 LinkCache
::singleton()->addBadLinkObj( $title );
164 $expected = wfGetLangObj( $expected );
166 $handler = ContentHandler
::getForTitle( $title );
167 $lang = $handler->getPageLanguage( $title );
169 $this->assertEquals( $expected->getCode(), $lang->getCode() );
172 public static function dataGetContentText_Null() {
181 * @dataProvider dataGetContentText_Null
182 * @covers ContentHandler::getContentText
184 public function testGetContentText_Null( $contentHandlerTextFallback ) {
185 $this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback );
189 $text = ContentHandler
::getContentText( $content );
190 $this->assertEquals( '', $text );
193 public static function dataGetContentText_TextContent() {
202 * @dataProvider dataGetContentText_TextContent
203 * @covers ContentHandler::getContentText
205 public function testGetContentText_TextContent( $contentHandlerTextFallback ) {
206 $this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback );
208 $content = new WikitextContent( "hello world" );
210 $text = ContentHandler
::getContentText( $content );
211 $this->assertEquals( $content->getNativeData(), $text );
215 * ContentHandler::getContentText should have thrown an exception for non-text Content object
216 * @expectedException MWException
217 * @covers ContentHandler::getContentText
219 public function testGetContentText_NonTextContent_fail() {
220 $this->setMwGlobals( 'wgContentHandlerTextFallback', 'fail' );
222 $content = new DummyContentForTesting( "hello world" );
224 ContentHandler
::getContentText( $content );
228 * @covers ContentHandler::getContentText
230 public function testGetContentText_NonTextContent_serialize() {
231 $this->setMwGlobals( 'wgContentHandlerTextFallback', 'serialize' );
233 $content = new DummyContentForTesting( "hello world" );
235 $text = ContentHandler
::getContentText( $content );
236 $this->assertEquals( $content->serialize(), $text );
240 * @covers ContentHandler::getContentText
242 public function testGetContentText_NonTextContent_ignore() {
243 $this->setMwGlobals( 'wgContentHandlerTextFallback', 'ignore' );
245 $content = new DummyContentForTesting( "hello world" );
247 $text = ContentHandler
::getContentText( $content );
248 $this->assertNull( $text );
251 public static function dataMakeContent() {
253 [ 'hallo', 'Help:Test', null, null, CONTENT_MODEL_WIKITEXT
, 'hallo', false ],
254 [ 'hallo', 'MediaWiki:Test.js', null, null, CONTENT_MODEL_JAVASCRIPT
, 'hallo', false ],
255 [ serialize( 'hallo' ), 'Dummy:Test', null, null, "testing", 'hallo', false ],
261 CONTENT_FORMAT_WIKITEXT
,
262 CONTENT_MODEL_WIKITEXT
,
270 CONTENT_FORMAT_JAVASCRIPT
,
271 CONTENT_MODEL_JAVASCRIPT
,
275 [ serialize( 'hallo' ), 'Dummy:Test', null, "testing", "testing", 'hallo', false ],
277 [ 'hallo', 'Help:Test', CONTENT_MODEL_CSS
, null, CONTENT_MODEL_CSS
, 'hallo', false ],
288 serialize( 'hallo' ),
293 serialize( 'hallo' ),
297 [ 'hallo', 'Help:Test', CONTENT_MODEL_WIKITEXT
, "testing", null, null, true ],
298 [ 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS
, "testing", null, null, true ],
299 [ 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT
, "testing", null, null, true ],
304 * @dataProvider dataMakeContent
305 * @covers ContentHandler::makeContent
307 public function testMakeContent( $data, $title, $modelId, $format,
308 $expectedModelId, $expectedNativeData, $shouldFail
310 $title = Title
::newFromText( $title );
311 LinkCache
::singleton()->addBadLinkObj( $title );
313 $content = ContentHandler
::makeContent( $data, $title, $modelId, $format );
316 $this->fail( "ContentHandler::makeContent should have failed!" );
319 $this->assertEquals( $expectedModelId, $content->getModel(), 'bad model id' );
320 $this->assertEquals( $expectedNativeData, $content->getNativeData(), 'bads native data' );
321 } catch ( MWException
$ex ) {
322 if ( !$shouldFail ) {
323 $this->fail( "ContentHandler::makeContent failed unexpectedly: " . $ex->getMessage() );
325 // dummy, so we don't get the "test did not perform any assertions" message.
326 $this->assertTrue( true );
332 * @covers ContentHandler::getAutosummary
334 * Test if we become a "Created blank page" summary from getAutoSummary if no Content added to
337 public function testGetAutosummary() {
338 $this->setMwGlobals( 'wgContLang', Language
::factory( 'en' ) );
340 $content = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT
);
341 $title = Title
::newFromText( 'Help:Test' );
342 // Create a new content object with no content
343 $newContent = ContentHandler
::makeContent( '', $title, CONTENT_MODEL_WIKITEXT
, null );
344 // first check, if we become a blank page created summary with the right bitmask
345 $autoSummary = $content->getAutosummary( null, $newContent, 97 );
346 $this->assertEquals( $autoSummary,
347 wfMessage( 'autosumm-newblank' )->inContentLanguage()->text() );
348 // now check, what we become with another bitmask
349 $autoSummary = $content->getAutosummary( null, $newContent, 92 );
350 $this->assertEquals( $autoSummary, '' );
354 * Test software tag that is added when content model of the page changes
355 * @covers ContentHandler::getChangeTag
357 public function testGetChangeTag() {
358 $this->setMwGlobals( 'wgSoftwareTags', [ 'mw-contentmodelchange' => true ] );
359 $wikitextContentHandler = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT
);
360 // Create old content object with javascript content model
361 $oldContent = ContentHandler
::makeContent( '', null, CONTENT_MODEL_JAVASCRIPT
, null );
362 // Create new content object with wikitext content model
363 $newContent = ContentHandler
::makeContent( '', null, CONTENT_MODEL_WIKITEXT
, null );
364 // Get the tag for this edit
365 $tag = $wikitextContentHandler->getChangeTag( $oldContent, $newContent, EDIT_UPDATE
);
366 $this->assertSame( $tag, 'mw-contentmodelchange' );
370 * @covers ContentHandler::supportsCategories
372 public function testSupportsCategories() {
373 $handler = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT
);
374 $this->assertTrue( $handler->supportsCategories(), 'content model supports categories' );
378 * @covers ContentHandler::supportsDirectEditing
380 public function testSupportsDirectEditing() {
381 $handler = new DummyContentHandlerForTesting( CONTENT_MODEL_JSON
);
382 $this->assertFalse( $handler->supportsDirectEditing(), 'direct editing is not supported' );
385 public static function dummyHookHandler( $foo, &$text, $bar ) {
386 if ( $text === null ||
$text === false ) {
390 $text = strtoupper( $text );
395 public function provideGetModelForID() {
397 [ CONTENT_MODEL_WIKITEXT
, WikitextContentHandler
::class ],
398 [ CONTENT_MODEL_JAVASCRIPT
, JavaScriptContentHandler
::class ],
399 [ CONTENT_MODEL_JSON
, JsonContentHandler
::class ],
400 [ CONTENT_MODEL_CSS
, CssContentHandler
::class ],
401 [ CONTENT_MODEL_TEXT
, TextContentHandler
::class ],
402 [ 'testing', DummyContentHandlerForTesting
::class ],
403 [ 'testing-callbacks', DummyContentHandlerForTesting
::class ],
408 * @covers ContentHandler::getForModelID
409 * @dataProvider provideGetModelForID
411 public function testGetModelForID( $modelId, $handlerClass ) {
412 $handler = ContentHandler
::getForModelID( $modelId );
414 $this->assertInstanceOf( $handlerClass, $handler );
418 * @covers ContentHandler::getFieldsForSearchIndex
420 public function testGetFieldsForSearchIndex() {
421 $searchEngine = $this->newSearchEngine();
423 $handler = ContentHandler
::getForModelID( CONTENT_MODEL_WIKITEXT
);
425 $fields = $handler->getFieldsForSearchIndex( $searchEngine );
427 $this->assertArrayHasKey( 'category', $fields );
428 $this->assertArrayHasKey( 'external_link', $fields );
429 $this->assertArrayHasKey( 'outgoing_link', $fields );
430 $this->assertArrayHasKey( 'template', $fields );
431 $this->assertArrayHasKey( 'content_model', $fields );
434 private function newSearchEngine() {
435 $searchEngine = $this->getMockBuilder( SearchEngine
::class )
438 $searchEngine->expects( $this->any() )
439 ->method( 'makeSearchFieldMapping' )
440 ->will( $this->returnCallback( function ( $name, $type ) {
441 return new DummySearchIndexFieldDefinition( $name, $type );
444 return $searchEngine;
448 * @covers ContentHandler::getDataForSearchIndex
450 public function testDataIndexFields() {
451 $mockEngine = $this->createMock( SearchEngine
::class );
452 $title = Title
::newFromText( 'Not_Main_Page', NS_MAIN
);
453 $page = new WikiPage( $title );
455 $this->setTemporaryHook( 'SearchDataForIndex',
458 ContentHandler
$handler,
460 ParserOutput
$output,
463 $fields['testDataField'] = 'test content';
466 $output = $page->getContent()->getParserOutput( $title );
467 $data = $page->getContentHandler()->getDataForSearchIndex( $page, $output, $mockEngine );
468 $this->assertArrayHasKey( 'text', $data );
469 $this->assertArrayHasKey( 'text_bytes', $data );
470 $this->assertArrayHasKey( 'language', $data );
471 $this->assertArrayHasKey( 'testDataField', $data );
472 $this->assertEquals( 'test content', $data['testDataField'] );
473 $this->assertEquals( 'wikitext', $data['content_model'] );
477 * @covers ContentHandler::getParserOutputForIndexing
479 public function testParserOutputForIndexing() {
480 $title = Title
::newFromText( 'Smithee', NS_MAIN
);
481 $page = new WikiPage( $title );
483 $out = $page->getContentHandler()->getParserOutputForIndexing( $page );
484 $this->assertInstanceOf( ParserOutput
::class, $out );
485 $this->assertContains( 'one who smiths', $out->getRawText() );
489 * @covers ContentHandler::getContentModels
491 public function testGetContentModelsHook() {
492 $this->setTemporaryHook( 'GetContentModels', function ( &$models ) {
493 $models[] = 'Ferrari';
495 $this->assertContains( 'Ferrari', ContentHandler
::getContentModels() );