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