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