Merge "Added Id to the input box"
[lhc/web/wiklou.git] / tests / phpunit / includes / api / ApiParseTest.php
1 <?php
2
3 /**
4 * @group API
5 * @group Database
6 * @group medium
7 *
8 * @covers ApiParse
9 */
10 class ApiParseTest extends ApiTestCase {
11
12 protected static $pageId;
13 protected static $revIds = [];
14
15 public function addDBDataOnce() {
16 $title = Title::newFromText( __CLASS__ );
17
18 $status = $this->editPage( __CLASS__, 'Test for revdel' );
19 self::$pageId = $status->value['revision']->getPage();
20 self::$revIds['revdel'] = $status->value['revision']->getId();
21
22 $status = $this->editPage( __CLASS__, 'Test for suppressed' );
23 self::$revIds['suppressed'] = $status->value['revision']->getId();
24
25 $status = $this->editPage( __CLASS__, 'Test for oldid' );
26 self::$revIds['oldid'] = $status->value['revision']->getId();
27
28 $status = $this->editPage( __CLASS__, 'Test for latest' );
29 self::$revIds['latest'] = $status->value['revision']->getId();
30
31 $this->revisionDelete( self::$revIds['revdel'] );
32 $this->revisionDelete(
33 self::$revIds['suppressed'],
34 [ Revision::DELETED_TEXT => 1, Revision::DELETED_RESTRICTED => 1 ]
35 );
36
37 Title::clearCaches(); // Otherwise it has the wrong latest revision for some reason
38 }
39
40 /**
41 * Assert that the given result of calling $this->doApiRequest() with
42 * action=parse resulted in $html, accounting for the boilerplate that the
43 * parser adds around the parsed page. Also asserts that warnings match
44 * the provided $warning.
45 *
46 * @param string $html Expected HTML
47 * @param array $res Returned from doApiRequest()
48 * @param string|null $warnings Exact value of expected warnings, null for
49 * no warnings
50 */
51 protected function assertParsedTo( $expected, array $res, $warnings = null ) {
52 $this->doAssertParsedTo( $expected, $res, $warnings, [ $this, 'assertSame' ] );
53 }
54
55 /**
56 * Same as above, but asserts that the HTML matches a regexp instead of a
57 * literal string match.
58 *
59 * @param string $html Expected HTML
60 * @param array $res Returned from doApiRequest()
61 * @param string|null $warnings Exact value of expected warnings, null for
62 * no warnings
63 */
64 protected function assertParsedToRegExp( $expected, array $res, $warnings = null ) {
65 $this->doAssertParsedTo( $expected, $res, $warnings, [ $this, 'assertRegExp' ] );
66 }
67
68 private function doAssertParsedTo( $expected, array $res, $warnings, callable $callback ) {
69 $html = $res[0]['parse']['text'];
70
71 $expectedStart = '<div class="mw-parser-output">';
72 $this->assertSame( $expectedStart, substr( $html, 0, strlen( $expectedStart ) ) );
73
74 $html = substr( $html, strlen( $expectedStart ) );
75
76 if ( $res[1]->getBool( 'disablelimitreport' ) ) {
77 $expectedEnd = "</div>";
78 $this->assertSame( $expectedEnd, substr( $html, -strlen( $expectedEnd ) ) );
79
80 $unexpectedEnd = '#<!-- \nNewPP limit report|' .
81 '<!--\nTransclusion expansion time report#s';
82 $this->assertNotRegExp( $unexpectedEnd, $html );
83
84 $html = substr( $html, 0, strlen( $html ) - strlen( $expectedEnd ) );
85 } else {
86 $expectedEnd = '#\n<!-- \nNewPP limit report\n(?>.+?\n-->)\n' .
87 '<!--\nTransclusion expansion time report \(%,ms,calls,template\)\n(?>.*?\n-->)\n' .
88 '(\n<!-- Saved in parser cache (?>.*?\n -->)\n)?</div>$#s';
89 $this->assertRegExp( $expectedEnd, $html );
90
91 $html = preg_replace( $expectedEnd, '', $html );
92 }
93
94 call_user_func( $callback, $expected, $html );
95
96 if ( $warnings === null ) {
97 $this->assertCount( 1, $res[0] );
98 } else {
99 $this->assertCount( 2, $res[0] );
100 $this->assertSame( [ 'warnings' => $warnings ], $res[0]['warnings']['parse'] );
101 }
102 }
103
104 /**
105 * Set up an interwiki entry for testing.
106 */
107 protected function setupInterwiki() {
108 $dbw = wfGetDB( DB_MASTER );
109 $dbw->insert(
110 'interwiki',
111 [
112 'iw_prefix' => 'madeuplanguage',
113 'iw_url' => "https://example.com/wiki/$1",
114 'iw_api' => '',
115 'iw_wikiid' => '',
116 'iw_local' => false,
117 ],
118 __METHOD__,
119 'IGNORE'
120 );
121
122 $this->setMwGlobals( 'wgExtraInterlanguageLinkPrefixes', [ 'madeuplanguage' ] );
123 $this->tablesUsed[] = 'interwiki';
124 }
125
126 /**
127 * Set up a skin for testing.
128 *
129 * @todo Should this code be in MediaWikiTestCase or something?
130 */
131 protected function setupSkin() {
132 $factory = new SkinFactory();
133 $factory->register( 'testing', 'Testing', function () {
134 $skin = $this->getMockBuilder( SkinFallback::class )
135 ->setMethods( [ 'getDefaultModules', 'setupSkinUserCss' ] )
136 ->getMock();
137 $skin->expects( $this->once() )->method( 'getDefaultModules' )
138 ->willReturn( [
139 'styles' => [ 'core' => [ 'quux.styles' ] ],
140 'core' => [ 'foo', 'bar' ],
141 'content' => [ 'baz' ]
142 ] );
143 $skin->expects( $this->once() )->method( 'setupSkinUserCss' )
144 ->will( $this->returnCallback( function ( OutputPage $out ) {
145 $out->addModuleStyles( 'foo.styles' );
146 } ) );
147 return $skin;
148 } );
149 $this->setService( 'SkinFactory', $factory );
150 }
151
152 public function testParseByName() {
153 $res = $this->doApiRequest( [
154 'action' => 'parse',
155 'page' => __CLASS__,
156 ] );
157 $this->assertParsedTo( "<p>Test for latest\n</p>", $res );
158
159 $res = $this->doApiRequest( [
160 'action' => 'parse',
161 'page' => __CLASS__,
162 'disablelimitreport' => 1,
163 ] );
164 $this->assertParsedTo( "<p>Test for latest\n</p>", $res );
165 }
166
167 public function testParseById() {
168 $res = $this->doApiRequest( [
169 'action' => 'parse',
170 'pageid' => self::$pageId,
171 ] );
172 $this->assertParsedTo( "<p>Test for latest\n</p>", $res );
173 }
174
175 public function testParseByOldId() {
176 $res = $this->doApiRequest( [
177 'action' => 'parse',
178 'oldid' => self::$revIds['oldid'],
179 ] );
180 $this->assertParsedTo( "<p>Test for oldid\n</p>", $res );
181 $this->assertArrayNotHasKey( 'textdeleted', $res[0]['parse'] );
182 $this->assertArrayNotHasKey( 'textsuppressed', $res[0]['parse'] );
183 }
184
185 public function testRevDel() {
186 $res = $this->doApiRequest( [
187 'action' => 'parse',
188 'oldid' => self::$revIds['revdel'],
189 ] );
190
191 $this->assertParsedTo( "<p>Test for revdel\n</p>", $res );
192 $this->assertArrayHasKey( 'textdeleted', $res[0]['parse'] );
193 $this->assertArrayNotHasKey( 'textsuppressed', $res[0]['parse'] );
194 }
195
196 public function testRevDelNoPermission() {
197 $this->setExpectedException( ApiUsageException::class,
198 "You don't have permission to view deleted revision text." );
199
200 $this->doApiRequest( [
201 'action' => 'parse',
202 'oldid' => self::$revIds['revdel'],
203 ], null, null, static::getTestUser()->getUser() );
204 }
205
206 public function testSuppressed() {
207 $this->setGroupPermissions( 'sysop', 'viewsuppressed', true );
208
209 $res = $this->doApiRequest( [
210 'action' => 'parse',
211 'oldid' => self::$revIds['suppressed']
212 ] );
213
214 $this->assertParsedTo( "<p>Test for suppressed\n</p>", $res );
215 $this->assertArrayHasKey( 'textsuppressed', $res[0]['parse'] );
216 $this->assertArrayHasKey( 'textdeleted', $res[0]['parse'] );
217 }
218
219 public function testNonexistentPage() {
220 try {
221 $this->doApiRequest( [
222 'action' => 'parse',
223 'page' => 'DoesNotExist',
224 ] );
225
226 $this->fail( "API did not return an error when parsing a nonexistent page" );
227 } catch ( ApiUsageException $ex ) {
228 $this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'missingtitle' ),
229 "Parse request for nonexistent page must give 'missingtitle' error: "
230 . var_export( self::getErrorFormatter()->arrayFromStatus( $ex->getStatusValue() ), true )
231 );
232 }
233 }
234
235 public function testTitleProvided() {
236 $res = $this->doApiRequest( [
237 'action' => 'parse',
238 'title' => 'Some interesting page',
239 'text' => '{{PAGENAME}} has attracted my attention',
240 ] );
241
242 $this->assertParsedTo( "<p>Some interesting page has attracted my attention\n</p>", $res );
243 }
244
245 public function testSection() {
246 $name = ucfirst( __FUNCTION__ );
247
248 $this->editPage( $name,
249 "Intro\n\n== Section 1 ==\n\nContent 1\n\n== Section 2 ==\n\nContent 2" );
250
251 $res = $this->doApiRequest( [
252 'action' => 'parse',
253 'page' => $name,
254 'section' => 1,
255 ] );
256
257 $this->assertParsedToRegExp( '!<h2>.*Section 1.*</h2>\n<p>Content 1\n</p>!', $res );
258 }
259
260 public function testInvalidSection() {
261 $this->setExpectedException( ApiUsageException::class,
262 'The "section" parameter must be a valid section ID or "new".' );
263
264 $this->doApiRequest( [
265 'action' => 'parse',
266 'section' => 'T-new',
267 ] );
268 }
269
270 public function testSectionNoContent() {
271 $name = ucfirst( __FUNCTION__ );
272
273 $status = $this->editPage( $name,
274 "Intro\n\n== Section 1 ==\n\nContent 1\n\n== Section 2 ==\n\nContent 2" );
275
276 $this->setExpectedException( ApiUsageException::class,
277 "Missing content for page ID {$status->value['revision']->getPage()}." );
278
279 $this->db->delete( 'revision', [ 'rev_id' => $status->value['revision']->getId() ] );
280
281 // Suppress warning in WikiPage::getContentModel
282 Wikimedia\suppressWarnings();
283 try {
284 $this->doApiRequest( [
285 'action' => 'parse',
286 'page' => $name,
287 'section' => 1,
288 ] );
289 } finally {
290 Wikimedia\restoreWarnings();
291 }
292 }
293
294 public function testNewSectionWithPage() {
295 $this->setExpectedException( ApiUsageException::class,
296 '"section=new" cannot be combined with the "oldid", "pageid" or "page" ' .
297 'parameters. Please use "title" and "text".' );
298
299 $this->doApiRequest( [
300 'action' => 'parse',
301 'page' => __CLASS__,
302 'section' => 'new',
303 ] );
304 }
305
306 public function testNonexistentOldId() {
307 $this->setExpectedException( ApiUsageException::class,
308 'There is no revision with ID 2147483647.' );
309
310 $this->doApiRequest( [
311 'action' => 'parse',
312 'oldid' => pow( 2, 31 ) - 1,
313 ] );
314 }
315
316 public function testUnfollowedRedirect() {
317 $name = ucfirst( __FUNCTION__ );
318
319 $this->editPage( $name, "#REDIRECT [[$name 2]]" );
320 $this->editPage( "$name 2", "Some ''text''" );
321
322 $res = $this->doApiRequest( [
323 'action' => 'parse',
324 'page' => $name,
325 ] );
326
327 // Can't use assertParsedTo because the parser output is different for
328 // redirects
329 $this->assertRegExp( "/Redirect to:.*$name 2/", $res[0]['parse']['text'] );
330 $this->assertArrayNotHasKey( 'warnings', $res[0] );
331 }
332
333 public function testFollowedRedirect() {
334 $name = ucfirst( __FUNCTION__ );
335
336 $this->editPage( $name, "#REDIRECT [[$name 2]]" );
337 $this->editPage( "$name 2", "Some ''text''" );
338
339 $res = $this->doApiRequest( [
340 'action' => 'parse',
341 'page' => $name,
342 'redirects' => true,
343 ] );
344
345 $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res );
346 }
347
348 public function testFollowedRedirectById() {
349 $name = ucfirst( __FUNCTION__ );
350
351 $id = $this->editPage( $name, "#REDIRECT [[$name 2]]" )->value['revision']->getPage();
352 $this->editPage( "$name 2", "Some ''text''" );
353
354 $res = $this->doApiRequest( [
355 'action' => 'parse',
356 'pageid' => $id,
357 'redirects' => true,
358 ] );
359
360 $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res );
361 }
362
363 public function testInvalidTitle() {
364 $this->setExpectedException( ApiUsageException::class, 'Bad title "|".' );
365
366 $this->doApiRequest( [
367 'action' => 'parse',
368 'title' => '|',
369 ] );
370 }
371
372 public function testTitleWithNonexistentRevId() {
373 $this->setExpectedException( ApiUsageException::class,
374 'There is no revision with ID 2147483647.' );
375
376 $this->doApiRequest( [
377 'action' => 'parse',
378 'title' => __CLASS__,
379 'revid' => pow( 2, 31 ) - 1,
380 ] );
381 }
382
383 public function testTitleWithNonMatchingRevId() {
384 $name = ucfirst( __FUNCTION__ );
385
386 $res = $this->doApiRequest( [
387 'action' => 'parse',
388 'title' => $name,
389 'revid' => self::$revIds['latest'],
390 'text' => 'Some text',
391 ] );
392
393 $this->assertParsedTo( "<p>Some text\n</p>", $res,
394 'r' . self::$revIds['latest'] . " is not a revision of $name." );
395 }
396
397 public function testRevId() {
398 $res = $this->doApiRequest( [
399 'action' => 'parse',
400 'revid' => self::$revIds['latest'],
401 'text' => 'My revid is {{REVISIONID}}!',
402 ] );
403
404 $this->assertParsedTo( "<p>My revid is " . self::$revIds['latest'] . "!\n</p>", $res );
405 }
406
407 public function testTitleNoText() {
408 $res = $this->doApiRequest( [
409 'action' => 'parse',
410 'title' => 'Special:AllPages',
411 ] );
412
413 $this->assertParsedTo( '', $res,
414 '"title" used without "text", and parsed page properties were requested. ' .
415 'Did you mean to use "page" instead of "title"?' );
416 }
417
418 public function testRevidNoText() {
419 $res = $this->doApiRequest( [
420 'action' => 'parse',
421 'revid' => self::$revIds['latest'],
422 ] );
423
424 $this->assertParsedTo( '', $res,
425 '"revid" used without "text", and parsed page properties were requested. ' .
426 'Did you mean to use "oldid" instead of "revid"?' );
427 }
428
429 public function testTextNoContentModel() {
430 $res = $this->doApiRequest( [
431 'action' => 'parse',
432 'text' => "Some ''text''",
433 ] );
434
435 $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res,
436 'No "title" or "contentmodel" was given, assuming wikitext.' );
437 }
438
439 public function testSerializationError() {
440 $this->setExpectedException( APIUsageException::class,
441 'Content serialization failed: Could not unserialize content' );
442
443 $this->mergeMwGlobalArrayValue( 'wgContentHandlers',
444 [ 'testing-serialize-error' => 'DummySerializeErrorContentHandler' ] );
445
446 $this->doApiRequest( [
447 'action' => 'parse',
448 'text' => "Some ''text''",
449 'contentmodel' => 'testing-serialize-error',
450 ] );
451 }
452
453 public function testNewSection() {
454 $res = $this->doApiRequest( [
455 'action' => 'parse',
456 'title' => __CLASS__,
457 'section' => 'new',
458 'sectiontitle' => 'Title',
459 'text' => 'Content',
460 ] );
461
462 $this->assertParsedToRegExp( '!<h2>.*Title.*</h2>\n<p>Content\n</p>!', $res );
463 }
464
465 public function testExistingSection() {
466 $res = $this->doApiRequest( [
467 'action' => 'parse',
468 'title' => __CLASS__,
469 'section' => 1,
470 'text' => "Intro\n\n== Section 1 ==\n\nContent\n\n== Section 2 ==\n\nMore content",
471 ] );
472
473 $this->assertParsedToRegExp( '!<h2>.*Section 1.*</h2>\n<p>Content\n</p>!', $res );
474 }
475
476 public function testNoPst() {
477 $name = ucfirst( __FUNCTION__ );
478
479 $this->editPage( "Template:$name", "Template ''text''" );
480
481 $res = $this->doApiRequest( [
482 'action' => 'parse',
483 'text' => "{{subst:$name}}",
484 'contentmodel' => 'wikitext',
485 ] );
486
487 $this->assertParsedTo( "<p>{{subst:$name}}\n</p>", $res );
488 }
489
490 public function testPst() {
491 $name = ucfirst( __FUNCTION__ );
492
493 $this->editPage( "Template:$name", "Template ''text''" );
494
495 $res = $this->doApiRequest( [
496 'action' => 'parse',
497 'pst' => '',
498 'text' => "{{subst:$name}}",
499 'contentmodel' => 'wikitext',
500 'prop' => 'text|wikitext',
501 ] );
502
503 $this->assertParsedTo( "<p>Template <i>text</i>\n</p>", $res );
504 $this->assertSame( "{{subst:$name}}", $res[0]['parse']['wikitext'] );
505 }
506
507 public function testOnlyPst() {
508 $name = ucfirst( __FUNCTION__ );
509
510 $this->editPage( "Template:$name", "Template ''text''" );
511
512 $res = $this->doApiRequest( [
513 'action' => 'parse',
514 'onlypst' => '',
515 'text' => "{{subst:$name}}",
516 'contentmodel' => 'wikitext',
517 'prop' => 'text|wikitext',
518 'summary' => 'Summary',
519 ] );
520
521 $this->assertSame(
522 [ 'parse' => [
523 'text' => "Template ''text''",
524 'wikitext' => "{{subst:$name}}",
525 'parsedsummary' => 'Summary',
526 ] ],
527 $res[0]
528 );
529 }
530
531 public function testHeadHtml() {
532 $res = $this->doApiRequest( [
533 'action' => 'parse',
534 'page' => __CLASS__,
535 'prop' => 'headhtml',
536 ] );
537
538 // Just do a rough sanity check
539 $this->assertRegExp( '#<!DOCTYPE.*<html.*<head.*</head>.*<body#s',
540 $res[0]['parse']['headhtml'] );
541 $this->assertArrayNotHasKey( 'warnings', $res[0] );
542 }
543
544 public function testCategoriesHtml() {
545 $name = ucfirst( __FUNCTION__ );
546
547 $this->editPage( $name, "[[Category:$name]]" );
548
549 $res = $this->doApiRequest( [
550 'action' => 'parse',
551 'page' => $name,
552 'prop' => 'categorieshtml',
553 ] );
554
555 $this->assertRegExp( "#Category.*Category:$name.*$name#",
556 $res[0]['parse']['categorieshtml'] );
557 $this->assertArrayNotHasKey( 'warnings', $res[0] );
558 }
559
560 public function testEffectiveLangLinks() {
561 $hookRan = false;
562 $this->setTemporaryHook( 'LanguageLinks',
563 function () use ( &$hookRan ) {
564 $hookRan = true;
565 }
566 );
567
568 $res = $this->doApiRequest( [
569 'action' => 'parse',
570 'title' => __CLASS__,
571 'text' => '[[zh:' . __CLASS__ . ']]',
572 'effectivelanglinks' => '',
573 ] );
574
575 $this->assertTrue( $hookRan );
576 $this->assertSame( 'The parameter "effectivelanglinks" has been deprecated.',
577 $res[0]['warnings']['parse']['warnings'] );
578 }
579
580 /**
581 * @param array $arr Extra params to add to API request
582 */
583 private function doTestLangLinks( array $arr = [] ) {
584 $this->setupInterwiki();
585
586 $res = $this->doApiRequest( array_merge( [
587 'action' => 'parse',
588 'title' => 'Omelette',
589 'text' => '[[madeuplanguage:Omelette]]',
590 'prop' => 'langlinks',
591 ], $arr ) );
592
593 $langLinks = $res[0]['parse']['langlinks'];
594
595 $this->assertCount( 1, $langLinks );
596 $this->assertSame( 'madeuplanguage', $langLinks[0]['lang'] );
597 $this->assertSame( 'Omelette', $langLinks[0]['title'] );
598 $this->assertSame( 'https://example.com/wiki/Omelette', $langLinks[0]['url'] );
599 $this->assertArrayNotHasKey( 'warnings', $res[0] );
600 }
601
602 public function testLangLinks() {
603 $this->doTestLangLinks();
604 }
605
606 public function testLangLinksWithSkin() {
607 $this->setupSkin();
608 $this->doTestLangLinks( [ 'useskin' => 'testing' ] );
609 }
610
611 public function testHeadItems() {
612 $res = $this->doApiRequest( [
613 'action' => 'parse',
614 'title' => __CLASS__,
615 'text' => '',
616 'prop' => 'headitems',
617 ] );
618
619 $this->assertSame( [], $res[0]['parse']['headitems'] );
620 $this->assertSame(
621 '"prop=headitems" is deprecated since MediaWiki 1.28. ' .
622 'Use "prop=headhtml" when creating new HTML documents, ' .
623 'or "prop=modules|jsconfigvars" when updating a document client-side.',
624 $res[0]['warnings']['parse']['warnings']
625 );
626 }
627
628 public function testHeadItemsWithSkin() {
629 $this->setupSkin();
630
631 $res = $this->doApiRequest( [
632 'action' => 'parse',
633 'title' => __CLASS__,
634 'text' => '',
635 'prop' => 'headitems',
636 'useskin' => 'testing',
637 ] );
638
639 $this->assertSame( [], $res[0]['parse']['headitems'] );
640 $this->assertSame(
641 '"prop=headitems" is deprecated since MediaWiki 1.28. ' .
642 'Use "prop=headhtml" when creating new HTML documents, ' .
643 'or "prop=modules|jsconfigvars" when updating a document client-side.',
644 $res[0]['warnings']['parse']['warnings']
645 );
646 }
647
648 public function testModules() {
649 $this->setTemporaryHook( 'ParserAfterParse',
650 function ( $parser ) {
651 $output = $parser->getOutput();
652 $output->addModules( [ 'foo', 'bar' ] );
653 $output->addModuleStyles( [ 'aaa', 'zzz' ] );
654 $output->addJsConfigVars( [ 'x' => 'y', 'z' => -3 ] );
655 }
656 );
657 $res = $this->doApiRequest( [
658 'action' => 'parse',
659 'title' => __CLASS__,
660 'text' => 'Content',
661 'prop' => 'modules|jsconfigvars|encodedjsconfigvars',
662 ] );
663
664 $this->assertSame( [ 'foo', 'bar' ], $res[0]['parse']['modules'] );
665 $this->assertSame( [], $res[0]['parse']['modulescripts'] );
666 $this->assertSame( [ 'aaa', 'zzz' ], $res[0]['parse']['modulestyles'] );
667 $this->assertSame( [ 'x' => 'y', 'z' => -3 ], $res[0]['parse']['jsconfigvars'] );
668 $this->assertSame( '{"x":"y","z":-3}', $res[0]['parse']['encodedjsconfigvars'] );
669 $this->assertArrayNotHasKey( 'warnings', $res[0] );
670 }
671
672 public function testModulesWithSkin() {
673 $this->setupSkin();
674
675 $res = $this->doApiRequest( [
676 'action' => 'parse',
677 'pageid' => self::$pageId,
678 'useskin' => 'testing',
679 'prop' => 'modules',
680 ] );
681 $this->assertSame(
682 [ 'foo', 'bar', 'baz' ],
683 $res[0]['parse']['modules'],
684 'resp.parse.modules'
685 );
686 $this->assertSame(
687 [],
688 $res[0]['parse']['modulescripts'],
689 'resp.parse.modulescripts'
690 );
691 $this->assertSame(
692 [ 'foo.styles', 'quux.styles' ],
693 $res[0]['parse']['modulestyles'],
694 'resp.parse.modulestyles'
695 );
696 $this->assertSame(
697 [ 'parse' =>
698 [ 'warnings' =>
699 'Property "modules" was set but not "jsconfigvars" or ' .
700 '"encodedjsconfigvars". Configuration variables are necessary for ' .
701 'proper module usage.'
702 ]
703 ],
704 $res[0]['warnings']
705 );
706 }
707
708 public function testIndicators() {
709 $res = $this->doApiRequest( [
710 'action' => 'parse',
711 'title' => __CLASS__,
712 'text' =>
713 '<indicator name="b">BBB!</indicator>Some text<indicator name="a">aaa</indicator>',
714 'prop' => 'indicators',
715 ] );
716
717 $this->assertSame(
718 // It seems we return in markup order and not display order
719 [ 'b' => 'BBB!', 'a' => 'aaa' ],
720 $res[0]['parse']['indicators']
721 );
722 $this->assertArrayNotHasKey( 'warnings', $res[0] );
723 }
724
725 public function testIndicatorsWithSkin() {
726 $this->setupSkin();
727
728 $res = $this->doApiRequest( [
729 'action' => 'parse',
730 'title' => __CLASS__,
731 'text' =>
732 '<indicator name="b">BBB!</indicator>Some text<indicator name="a">aaa</indicator>',
733 'prop' => 'indicators',
734 'useskin' => 'testing',
735 ] );
736
737 $this->assertSame(
738 // Now we return in display order rather than markup order
739 [ 'a' => 'aaa', 'b' => 'BBB!' ],
740 $res[0]['parse']['indicators']
741 );
742 $this->assertArrayNotHasKey( 'warnings', $res[0] );
743 }
744
745 public function testIwlinks() {
746 $this->setupInterwiki();
747
748 $res = $this->doApiRequest( [
749 'action' => 'parse',
750 'title' => 'Omelette',
751 'text' => '[[:madeuplanguage:Omelette]][[madeuplanguage:Spaghetti]]',
752 'prop' => 'iwlinks',
753 ] );
754
755 $iwlinks = $res[0]['parse']['iwlinks'];
756
757 $this->assertCount( 1, $iwlinks );
758 $this->assertSame( 'madeuplanguage', $iwlinks[0]['prefix'] );
759 $this->assertSame( 'https://example.com/wiki/Omelette', $iwlinks[0]['url'] );
760 $this->assertSame( 'madeuplanguage:Omelette', $iwlinks[0]['title'] );
761 $this->assertArrayNotHasKey( 'warnings', $res[0] );
762 }
763
764 public function testLimitReports() {
765 $res = $this->doApiRequest( [
766 'action' => 'parse',
767 'pageid' => self::$pageId,
768 'prop' => 'limitreportdata|limitreporthtml',
769 ] );
770
771 // We don't bother testing the actual values here
772 $this->assertInternalType( 'array', $res[0]['parse']['limitreportdata'] );
773 $this->assertInternalType( 'string', $res[0]['parse']['limitreporthtml'] );
774 $this->assertArrayNotHasKey( 'warnings', $res[0] );
775 }
776
777 public function testParseTreeNonWikitext() {
778 $this->setExpectedException( ApiUsageException::class,
779 '"prop=parsetree" is only supported for wikitext content.' );
780
781 $this->doApiRequest( [
782 'action' => 'parse',
783 'text' => '',
784 'contentmodel' => 'json',
785 'prop' => 'parsetree',
786 ] );
787 }
788
789 public function testParseTree() {
790 $res = $this->doApiRequest( [
791 'action' => 'parse',
792 'text' => "Some ''text'' is {{nice|to have|i=think}}",
793 'contentmodel' => 'wikitext',
794 'prop' => 'parsetree',
795 ] );
796
797 // Preprocessor_DOM and Preprocessor_Hash give different results here,
798 // so we'll accept either
799 $this->assertRegExp(
800 '#^<root>Some \'\'text\'\' is <template><title>nice</title>' .
801 '<part><name index="1"/><value>to have</value></part>' .
802 '<part><name>i</name>(?:<equals>)?=(?:</equals>)?<value>think</value></part>' .
803 '</template></root>$#',
804 $res[0]['parse']['parsetree']
805 );
806 $this->assertArrayNotHasKey( 'warnings', $res[0] );
807 }
808
809 public function testDisableTidy() {
810 $this->setMwGlobals( 'wgTidyConfig', [ 'driver' => 'RemexHtml' ] );
811
812 // Check that disabletidy doesn't have an effect just because tidying
813 // doesn't work for some other reason
814 $res1 = $this->doApiRequest( [
815 'action' => 'parse',
816 'text' => "<b>Mixed <i>up</b></i>",
817 'contentmodel' => 'wikitext',
818 ] );
819 $this->assertParsedTo( "<p><b>Mixed <i>up</i></b>\n</p>", $res1 );
820
821 $res2 = $this->doApiRequest( [
822 'action' => 'parse',
823 'text' => "<b>Mixed <i>up</b></i>",
824 'contentmodel' => 'wikitext',
825 'disabletidy' => '',
826 ] );
827
828 $this->assertParsedTo( "<p><b>Mixed <i>up</b></i>\n</p>", $res2,
829 'The parameter "disabletidy" has been deprecated.' );
830 }
831
832 public function testFormatCategories() {
833 $name = ucfirst( __FUNCTION__ );
834
835 $this->editPage( "Category:$name", 'Content' );
836 $this->editPage( 'Category:Hidden', '__HIDDENCAT__' );
837
838 $res = $this->doApiRequest( [
839 'action' => 'parse',
840 'title' => __CLASS__,
841 'text' => "[[Category:$name]][[Category:Foo|Sort me]][[Category:Hidden]]",
842 'prop' => 'categories',
843 ] );
844
845 $this->assertSame(
846 [ [ 'sortkey' => '', 'category' => $name ],
847 [ 'sortkey' => 'Sort me', 'category' => 'Foo', 'missing' => true ],
848 [ 'sortkey' => '', 'category' => 'Hidden', 'hidden' => true ] ],
849 $res[0]['parse']['categories']
850 );
851 $this->assertArrayNotHasKey( 'warnings', $res[0] );
852 }
853 }