Merge "Change php extract() to explicit code"
[lhc/web/wiklou.git] / tests / phpunit / includes / content / ContentHandlerTest.php
1 <?php
2 use MediaWiki\MediaWikiServices;
3
4 /**
5 * @group ContentHandler
6 * @group Database
7 */
8 class ContentHandlerTest extends MediaWikiTestCase {
9
10 protected function setUp() {
11 global $wgContLang;
12 parent::setUp();
13
14 $this->setMwGlobals( [
15 'wgExtraNamespaces' => [
16 12312 => 'Dummy',
17 12313 => 'Dummy_talk',
18 ],
19 // The below tests assume that namespaces not mentioned here (Help, User, MediaWiki, ..)
20 // default to CONTENT_MODEL_WIKITEXT.
21 'wgNamespaceContentModels' => [
22 12312 => 'testing',
23 ],
24 'wgContentHandlers' => [
25 CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler',
26 CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler',
27 CONTENT_MODEL_JSON => 'JsonContentHandler',
28 CONTENT_MODEL_CSS => 'CssContentHandler',
29 CONTENT_MODEL_TEXT => 'TextContentHandler',
30 'testing' => 'DummyContentHandlerForTesting',
31 'testing-callbacks' => function ( $modelId ) {
32 return new DummyContentHandlerForTesting( $modelId );
33 }
34 ],
35 ] );
36
37 // Reset namespace cache
38 MWNamespace::clearCaches();
39 $wgContLang->resetNamespaces();
40 // And LinkCache
41 MediaWikiServices::getInstance()->resetServiceForTesting( 'LinkCache' );
42 }
43
44 protected function tearDown() {
45 global $wgContLang;
46
47 // Reset namespace cache
48 MWNamespace::clearCaches();
49 $wgContLang->resetNamespaces();
50 // And LinkCache
51 MediaWikiServices::getInstance()->resetServiceForTesting( 'LinkCache' );
52
53 parent::tearDown();
54 }
55
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]]' );
59 }
60
61 public static function dataGetDefaultModelFor() {
62 return [
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 ],
86 ];
87 }
88
89 /**
90 * @dataProvider dataGetDefaultModelFor
91 * @covers ContentHandler::getDefaultModelFor
92 */
93 public function testGetDefaultModelFor( $title, $expectedModelId ) {
94 $title = Title::newFromText( $title );
95 $this->assertEquals( $expectedModelId, ContentHandler::getDefaultModelFor( $title ) );
96 }
97
98 /**
99 * @dataProvider dataGetDefaultModelFor
100 * @covers ContentHandler::getForTitle
101 */
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() );
107 }
108
109 public static function dataGetLocalizedName() {
110 return [
111 [ null, null ],
112 [ "xyzzy", null ],
113
114 // XXX: depends on content language
115 [ CONTENT_MODEL_JAVASCRIPT, '/javascript/i' ],
116 ];
117 }
118
119 /**
120 * @dataProvider dataGetLocalizedName
121 * @covers ContentHandler::getLocalizedName
122 */
123 public function testGetLocalizedName( $id, $expected ) {
124 $name = ContentHandler::getLocalizedName( $id );
125
126 if ( $expected ) {
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"
130 );
131 } else {
132 $this->assertEquals( $id, $name, "localization of unknown model $id should have "
133 . "fallen back to use the model id directly."
134 );
135 }
136 }
137
138 public static function dataGetPageLanguage() {
139 global $wgLanguageCode;
140
141 return [
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 ],
149
150 [ CONTENT_MODEL_JAVASCRIPT, 'javascript' ],
151 ];
152 }
153
154 /**
155 * @dataProvider dataGetPageLanguage
156 * @covers ContentHandler::getPageLanguage
157 */
158 public function testGetPageLanguage( $title, $expected ) {
159 if ( is_string( $title ) ) {
160 $title = Title::newFromText( $title );
161 LinkCache::singleton()->addBadLinkObj( $title );
162 }
163
164 $expected = wfGetLangObj( $expected );
165
166 $handler = ContentHandler::getForTitle( $title );
167 $lang = $handler->getPageLanguage( $title );
168
169 $this->assertEquals( $expected->getCode(), $lang->getCode() );
170 }
171
172 public static function dataGetContentText_Null() {
173 return [
174 [ 'fail' ],
175 [ 'serialize' ],
176 [ 'ignore' ],
177 ];
178 }
179
180 /**
181 * @dataProvider dataGetContentText_Null
182 * @covers ContentHandler::getContentText
183 */
184 public function testGetContentText_Null( $contentHandlerTextFallback ) {
185 $this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback );
186
187 $content = null;
188
189 $text = ContentHandler::getContentText( $content );
190 $this->assertEquals( '', $text );
191 }
192
193 public static function dataGetContentText_TextContent() {
194 return [
195 [ 'fail' ],
196 [ 'serialize' ],
197 [ 'ignore' ],
198 ];
199 }
200
201 /**
202 * @dataProvider dataGetContentText_TextContent
203 * @covers ContentHandler::getContentText
204 */
205 public function testGetContentText_TextContent( $contentHandlerTextFallback ) {
206 $this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback );
207
208 $content = new WikitextContent( "hello world" );
209
210 $text = ContentHandler::getContentText( $content );
211 $this->assertEquals( $content->getNativeData(), $text );
212 }
213
214 /**
215 * ContentHandler::getContentText should have thrown an exception for non-text Content object
216 * @expectedException MWException
217 * @covers ContentHandler::getContentText
218 */
219 public function testGetContentText_NonTextContent_fail() {
220 $this->setMwGlobals( 'wgContentHandlerTextFallback', 'fail' );
221
222 $content = new DummyContentForTesting( "hello world" );
223
224 ContentHandler::getContentText( $content );
225 }
226
227 /**
228 * @covers ContentHandler::getContentText
229 */
230 public function testGetContentText_NonTextContent_serialize() {
231 $this->setMwGlobals( 'wgContentHandlerTextFallback', 'serialize' );
232
233 $content = new DummyContentForTesting( "hello world" );
234
235 $text = ContentHandler::getContentText( $content );
236 $this->assertEquals( $content->serialize(), $text );
237 }
238
239 /**
240 * @covers ContentHandler::getContentText
241 */
242 public function testGetContentText_NonTextContent_ignore() {
243 $this->setMwGlobals( 'wgContentHandlerTextFallback', 'ignore' );
244
245 $content = new DummyContentForTesting( "hello world" );
246
247 $text = ContentHandler::getContentText( $content );
248 $this->assertNull( $text );
249 }
250
251 /*
252 public static function makeContent( $text, Title $title, $modelId = null, $format = null ) {}
253 */
254
255 public static function dataMakeContent() {
256 return [
257 [ 'hallo', 'Help:Test', null, null, CONTENT_MODEL_WIKITEXT, 'hallo', false ],
258 [ 'hallo', 'MediaWiki:Test.js', null, null, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ],
259 [ serialize( 'hallo' ), 'Dummy:Test', null, null, "testing", 'hallo', false ],
260
261 [
262 'hallo',
263 'Help:Test',
264 null,
265 CONTENT_FORMAT_WIKITEXT,
266 CONTENT_MODEL_WIKITEXT,
267 'hallo',
268 false
269 ],
270 [
271 'hallo',
272 'MediaWiki:Test.js',
273 null,
274 CONTENT_FORMAT_JAVASCRIPT,
275 CONTENT_MODEL_JAVASCRIPT,
276 'hallo',
277 false
278 ],
279 [ serialize( 'hallo' ), 'Dummy:Test', null, "testing", "testing", 'hallo', false ],
280
281 [ 'hallo', 'Help:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ],
282 [
283 'hallo',
284 'MediaWiki:Test.js',
285 CONTENT_MODEL_CSS,
286 null,
287 CONTENT_MODEL_CSS,
288 'hallo',
289 false
290 ],
291 [
292 serialize( 'hallo' ),
293 'Dummy:Test',
294 CONTENT_MODEL_CSS,
295 null,
296 CONTENT_MODEL_CSS,
297 serialize( 'hallo' ),
298 false
299 ],
300
301 [ 'hallo', 'Help:Test', CONTENT_MODEL_WIKITEXT, "testing", null, null, true ],
302 [ 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, "testing", null, null, true ],
303 [ 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT, "testing", null, null, true ],
304 ];
305 }
306
307 /**
308 * @dataProvider dataMakeContent
309 * @covers ContentHandler::makeContent
310 */
311 public function testMakeContent( $data, $title, $modelId, $format,
312 $expectedModelId, $expectedNativeData, $shouldFail
313 ) {
314 $title = Title::newFromText( $title );
315 LinkCache::singleton()->addBadLinkObj( $title );
316 try {
317 $content = ContentHandler::makeContent( $data, $title, $modelId, $format );
318
319 if ( $shouldFail ) {
320 $this->fail( "ContentHandler::makeContent should have failed!" );
321 }
322
323 $this->assertEquals( $expectedModelId, $content->getModel(), 'bad model id' );
324 $this->assertEquals( $expectedNativeData, $content->getNativeData(), 'bads native data' );
325 } catch ( MWException $ex ) {
326 if ( !$shouldFail ) {
327 $this->fail( "ContentHandler::makeContent failed unexpectedly: " . $ex->getMessage() );
328 } else {
329 // dummy, so we don't get the "test did not perform any assertions" message.
330 $this->assertTrue( true );
331 }
332 }
333 }
334
335 /**
336 * @covers ContentHandler::getAutosummary
337 *
338 * Test if we become a "Created blank page" summary from getAutoSummary if no Content added to
339 * page.
340 */
341 public function testGetAutosummary() {
342 $this->setMwGlobals( 'wgContLang', Language::factory( 'en' ) );
343
344 $content = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
345 $title = Title::newFromText( 'Help:Test' );
346 // Create a new content object with no content
347 $newContent = ContentHandler::makeContent( '', $title, CONTENT_MODEL_WIKITEXT, null );
348 // first check, if we become a blank page created summary with the right bitmask
349 $autoSummary = $content->getAutosummary( null, $newContent, 97 );
350 $this->assertEquals( $autoSummary,
351 wfMessage( 'autosumm-newblank' )->inContentLanguage()->text() );
352 // now check, what we become with another bitmask
353 $autoSummary = $content->getAutosummary( null, $newContent, 92 );
354 $this->assertEquals( $autoSummary, '' );
355 }
356
357 /**
358 * Test software tag that is added when content model of the page changes
359 * @covers ContentHandler::getChangeTag
360 */
361 public function testGetChangeTag() {
362 $this->setMwGlobals( 'wgSoftwareTags', [ 'mw-contentmodelchange' => true ] );
363 $wikitextContentHandler = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
364 // Create old content object with javascript content model
365 $oldContent = ContentHandler::makeContent( '', null, CONTENT_MODEL_JAVASCRIPT, null );
366 // Create new content object with wikitext content model
367 $newContent = ContentHandler::makeContent( '', null, CONTENT_MODEL_WIKITEXT, null );
368 // Get the tag for this edit
369 $tag = $wikitextContentHandler->getChangeTag( $oldContent, $newContent, EDIT_UPDATE );
370 $this->assertSame( $tag, 'mw-contentmodelchange' );
371 }
372
373 /*
374 public function testSupportsSections() {
375 $this->markTestIncomplete( "not yet implemented" );
376 }
377 */
378
379 /**
380 * @covers ContentHandler::supportsCategories
381 */
382 public function testSupportsCategories() {
383 $handler = new DummyContentHandlerForTesting( CONTENT_MODEL_WIKITEXT );
384 $this->assertTrue( $handler->supportsCategories(), 'content model supports categories' );
385 }
386
387 /**
388 * @covers ContentHandler::supportsDirectEditing
389 */
390 public function testSupportsDirectEditing() {
391 $handler = new DummyContentHandlerForTesting( CONTENT_MODEL_JSON );
392 $this->assertFalse( $handler->supportsDirectEditing(), 'direct editing is not supported' );
393 }
394
395 public static function dummyHookHandler( $foo, &$text, $bar ) {
396 if ( $text === null || $text === false ) {
397 return false;
398 }
399
400 $text = strtoupper( $text );
401
402 return true;
403 }
404
405 public function provideGetModelForID() {
406 return [
407 [ CONTENT_MODEL_WIKITEXT, 'WikitextContentHandler' ],
408 [ CONTENT_MODEL_JAVASCRIPT, 'JavaScriptContentHandler' ],
409 [ CONTENT_MODEL_JSON, 'JsonContentHandler' ],
410 [ CONTENT_MODEL_CSS, 'CssContentHandler' ],
411 [ CONTENT_MODEL_TEXT, 'TextContentHandler' ],
412 [ 'testing', 'DummyContentHandlerForTesting' ],
413 [ 'testing-callbacks', 'DummyContentHandlerForTesting' ],
414 ];
415 }
416
417 /**
418 * @covers ContentHandler::getForModelID
419 * @dataProvider provideGetModelForID
420 */
421 public function testGetModelForID( $modelId, $handlerClass ) {
422 $handler = ContentHandler::getForModelID( $modelId );
423
424 $this->assertInstanceOf( $handlerClass, $handler );
425 }
426
427 /**
428 * @covers ContentHandler::getFieldsForSearchIndex
429 */
430 public function testGetFieldsForSearchIndex() {
431 $searchEngine = $this->newSearchEngine();
432
433 $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
434
435 $fields = $handler->getFieldsForSearchIndex( $searchEngine );
436
437 $this->assertArrayHasKey( 'category', $fields );
438 $this->assertArrayHasKey( 'external_link', $fields );
439 $this->assertArrayHasKey( 'outgoing_link', $fields );
440 $this->assertArrayHasKey( 'template', $fields );
441 $this->assertArrayHasKey( 'content_model', $fields );
442 }
443
444 private function newSearchEngine() {
445 $searchEngine = $this->getMockBuilder( 'SearchEngine' )
446 ->getMock();
447
448 $searchEngine->expects( $this->any() )
449 ->method( 'makeSearchFieldMapping' )
450 ->will( $this->returnCallback( function ( $name, $type ) {
451 return new DummySearchIndexFieldDefinition( $name, $type );
452 } ) );
453
454 return $searchEngine;
455 }
456
457 /**
458 * @covers ContentHandler::getDataForSearchIndex
459 */
460 public function testDataIndexFields() {
461 $mockEngine = $this->createMock( 'SearchEngine' );
462 $title = Title::newFromText( 'Not_Main_Page', NS_MAIN );
463 $page = new WikiPage( $title );
464
465 $this->setTemporaryHook( 'SearchDataForIndex',
466 function (
467 &$fields,
468 ContentHandler $handler,
469 WikiPage $page,
470 ParserOutput $output,
471 SearchEngine $engine
472 ) {
473 $fields['testDataField'] = 'test content';
474 } );
475
476 $output = $page->getContent()->getParserOutput( $title );
477 $data = $page->getContentHandler()->getDataForSearchIndex( $page, $output, $mockEngine );
478 $this->assertArrayHasKey( 'text', $data );
479 $this->assertArrayHasKey( 'text_bytes', $data );
480 $this->assertArrayHasKey( 'language', $data );
481 $this->assertArrayHasKey( 'testDataField', $data );
482 $this->assertEquals( 'test content', $data['testDataField'] );
483 $this->assertEquals( 'wikitext', $data['content_model'] );
484 }
485
486 /**
487 * @covers ContentHandler::getParserOutputForIndexing
488 */
489 public function testParserOutputForIndexing() {
490 $title = Title::newFromText( 'Smithee', NS_MAIN );
491 $page = new WikiPage( $title );
492
493 $out = $page->getContentHandler()->getParserOutputForIndexing( $page );
494 $this->assertInstanceOf( ParserOutput::class, $out );
495 $this->assertContains( 'one who smiths', $out->getRawText() );
496 }
497
498 /**
499 * @covers ContentHandler::getContentModels
500 */
501 public function testGetContentModelsHook() {
502 $this->setTemporaryHook( 'GetContentModels', function ( &$models ) {
503 $models[] = 'Ferrari';
504 } );
505 $this->assertContains( 'Ferrari', ContentHandler::getContentModels() );
506 }
507 }