WikitextContentTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. <?php
  2. use MediaWiki\MediaWikiServices;
  3. /**
  4. * @group ContentHandler
  5. *
  6. * @group Database
  7. * ^--- needed, because we do need the database to test link updates
  8. */
  9. class WikitextContentTest extends TextContentTest {
  10. public static $sections = "Intro
  11. == stuff ==
  12. hello world
  13. == test ==
  14. just a test
  15. == foo ==
  16. more stuff
  17. ";
  18. public function newContent( $text ) {
  19. return new WikitextContent( $text );
  20. }
  21. public static function dataGetParserOutput() {
  22. return [
  23. [
  24. "WikitextContentTest_testGetParserOutput",
  25. CONTENT_MODEL_WIKITEXT,
  26. "hello ''world''\n",
  27. "<div class=\"mw-parser-output\"><p>hello <i>world</i>\n</p>\n\n\n</div>"
  28. ],
  29. // TODO: more...?
  30. ];
  31. }
  32. public static function dataGetSecondaryDataUpdates() {
  33. return [
  34. [ "WikitextContentTest_testGetSecondaryDataUpdates_1",
  35. CONTENT_MODEL_WIKITEXT, "hello ''world''\n",
  36. [
  37. LinksUpdate::class => [
  38. 'mRecursive' => true,
  39. 'mLinks' => []
  40. ]
  41. ]
  42. ],
  43. [ "WikitextContentTest_testGetSecondaryDataUpdates_2",
  44. CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n",
  45. [
  46. LinksUpdate::class => [
  47. 'mRecursive' => true,
  48. 'mLinks' => [
  49. [ 'World_test_21344' => 0 ]
  50. ]
  51. ]
  52. ]
  53. ],
  54. // TODO: more...?
  55. ];
  56. }
  57. /**
  58. * @dataProvider dataGetSecondaryDataUpdates
  59. * @group Database
  60. * @covers WikitextContent::getSecondaryDataUpdates
  61. */
  62. public function testGetSecondaryDataUpdates( $title, $model, $text, $expectedStuff ) {
  63. $ns = $this->getDefaultWikitextNS();
  64. $title = Title::newFromText( $title, $ns );
  65. $content = ContentHandler::makeContent( $text, $title, $model );
  66. $page = WikiPage::factory( $title );
  67. $page->doEditContent( $content, '' );
  68. $updates = $content->getSecondaryDataUpdates( $title );
  69. // make updates accessible by class name
  70. foreach ( $updates as $update ) {
  71. $class = get_class( $update );
  72. $updates[$class] = $update;
  73. }
  74. foreach ( $expectedStuff as $class => $fieldValues ) {
  75. $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" );
  76. $update = $updates[$class];
  77. foreach ( $fieldValues as $field => $value ) {
  78. $v = $update->$field; # if the field doesn't exist, just crash and burn
  79. $this->assertEquals(
  80. $value,
  81. $v,
  82. "unexpected value for field $field in instance of $class"
  83. );
  84. }
  85. }
  86. $page->doDeleteArticle( '' );
  87. }
  88. public static function dataGetSection() {
  89. return [
  90. [ self::$sections,
  91. "0",
  92. "Intro"
  93. ],
  94. [ self::$sections,
  95. "2",
  96. "== test ==
  97. just a test"
  98. ],
  99. [ self::$sections,
  100. "8",
  101. false
  102. ],
  103. ];
  104. }
  105. /**
  106. * @dataProvider dataGetSection
  107. * @covers WikitextContent::getSection
  108. */
  109. public function testGetSection( $text, $sectionId, $expectedText ) {
  110. $content = $this->newContent( $text );
  111. $sectionContent = $content->getSection( $sectionId );
  112. if ( is_object( $sectionContent ) ) {
  113. $sectionText = $sectionContent->getNativeData();
  114. } else {
  115. $sectionText = $sectionContent;
  116. }
  117. $this->assertEquals( $expectedText, $sectionText );
  118. }
  119. public static function dataReplaceSection() {
  120. return [
  121. [ self::$sections,
  122. "0",
  123. "No more",
  124. null,
  125. trim( preg_replace( '/^Intro/sm', 'No more', self::$sections ) )
  126. ],
  127. [ self::$sections,
  128. "",
  129. "No more",
  130. null,
  131. "No more"
  132. ],
  133. [ self::$sections,
  134. "2",
  135. "== TEST ==\nmore fun",
  136. null,
  137. trim( preg_replace(
  138. '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==",
  139. self::$sections
  140. ) )
  141. ],
  142. [ self::$sections,
  143. "8",
  144. "No more",
  145. null,
  146. self::$sections
  147. ],
  148. [ self::$sections,
  149. "new",
  150. "No more",
  151. "New",
  152. trim( self::$sections ) . "\n\n\n== New ==\n\nNo more"
  153. ],
  154. ];
  155. }
  156. /**
  157. * @dataProvider dataReplaceSection
  158. * @covers WikitextContent::replaceSection
  159. */
  160. public function testReplaceSection( $text, $section, $with, $sectionTitle, $expected ) {
  161. $content = $this->newContent( $text );
  162. $c = $content->replaceSection( $section, $this->newContent( $with ), $sectionTitle );
  163. $this->assertEquals( $expected, is_null( $c ) ? null : $c->getNativeData() );
  164. }
  165. /**
  166. * @covers WikitextContent::addSectionHeader
  167. */
  168. public function testAddSectionHeader() {
  169. $content = $this->newContent( 'hello world' );
  170. $content = $content->addSectionHeader( 'test' );
  171. $this->assertEquals( "== test ==\n\nhello world", $content->getNativeData() );
  172. }
  173. public static function dataPreSaveTransform() {
  174. return [
  175. [ 'hello this is ~~~',
  176. "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
  177. ],
  178. [ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
  179. 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
  180. ],
  181. [ // rtrim
  182. " Foo \n ",
  183. " Foo",
  184. ],
  185. ];
  186. }
  187. public static function dataPreloadTransform() {
  188. return [
  189. [
  190. 'hello this is ~~~',
  191. "hello this is ~~~",
  192. ],
  193. [
  194. 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>',
  195. 'hello \'\'this\'\' is bar',
  196. ],
  197. ];
  198. }
  199. public static function dataGetRedirectTarget() {
  200. return [
  201. [ '#REDIRECT [[Test]]',
  202. 'Test',
  203. ],
  204. [ '#REDIRECT Test',
  205. null,
  206. ],
  207. [ '* #REDIRECT [[Test]]',
  208. null,
  209. ],
  210. ];
  211. }
  212. public static function dataGetTextForSummary() {
  213. return [
  214. [ "hello\nworld.",
  215. 16,
  216. 'hello world.',
  217. ],
  218. [ 'hello world.',
  219. 8,
  220. 'hello...',
  221. ],
  222. [ '[[hello world]].',
  223. 8,
  224. 'hel...',
  225. ],
  226. ];
  227. }
  228. public static function dataIsCountable() {
  229. return [
  230. [ '',
  231. null,
  232. 'any',
  233. true
  234. ],
  235. [ 'Foo',
  236. null,
  237. 'any',
  238. true
  239. ],
  240. [ 'Foo',
  241. null,
  242. 'link',
  243. false
  244. ],
  245. [ 'Foo [[bar]]',
  246. null,
  247. 'link',
  248. true
  249. ],
  250. [ 'Foo',
  251. true,
  252. 'link',
  253. true
  254. ],
  255. [ 'Foo [[bar]]',
  256. false,
  257. 'link',
  258. false
  259. ],
  260. [ '#REDIRECT [[bar]]',
  261. true,
  262. 'any',
  263. false
  264. ],
  265. [ '#REDIRECT [[bar]]',
  266. true,
  267. 'link',
  268. false
  269. ],
  270. ];
  271. }
  272. /**
  273. * @covers WikitextContent::matchMagicWord
  274. */
  275. public function testMatchMagicWord() {
  276. $mw = MediaWikiServices::getInstance()->getMagicWordFactory()->get( "staticredirect" );
  277. $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" );
  278. $this->assertTrue( $content->matchMagicWord( $mw ), "should have matched magic word" );
  279. $content = $this->newContent( "#REDIRECT [[FOO]]" );
  280. $this->assertFalse(
  281. $content->matchMagicWord( $mw ),
  282. "should not have matched magic word"
  283. );
  284. }
  285. /**
  286. * @covers WikitextContent::updateRedirect
  287. */
  288. public function testUpdateRedirect() {
  289. $target = Title::newFromText( "testUpdateRedirect_target" );
  290. // test with non-redirect page
  291. $content = $this->newContent( "hello world." );
  292. $newContent = $content->updateRedirect( $target );
  293. $this->assertTrue( $content->equals( $newContent ), "content should be unchanged" );
  294. // test with actual redirect
  295. $content = $this->newContent( "#REDIRECT [[Someplace]]" );
  296. $newContent = $content->updateRedirect( $target );
  297. $this->assertFalse( $content->equals( $newContent ), "content should have changed" );
  298. $this->assertTrue( $newContent->isRedirect(), "new content should be a redirect" );
  299. $this->assertEquals(
  300. $target->getFullText(),
  301. $newContent->getRedirectTarget()->getFullText()
  302. );
  303. }
  304. /**
  305. * @covers WikitextContent::getModel
  306. */
  307. public function testGetModel() {
  308. $content = $this->newContent( "hello world." );
  309. $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getModel() );
  310. }
  311. /**
  312. * @covers WikitextContent::getContentHandler
  313. */
  314. public function testGetContentHandler() {
  315. $content = $this->newContent( "hello world." );
  316. $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getContentHandler()->getModelID() );
  317. }
  318. public function testRedirectParserOption() {
  319. $title = Title::newFromText( 'testRedirectParserOption' );
  320. // Set up hook and its reporting variables
  321. $wikitext = null;
  322. $redirectTarget = null;
  323. $this->mergeMwGlobalArrayValue( 'wgHooks', [
  324. 'InternalParseBeforeLinks' => [
  325. function ( &$parser, &$text, &$stripState ) use ( &$wikitext, &$redirectTarget ) {
  326. $wikitext = $text;
  327. $redirectTarget = $parser->getOptions()->getRedirectTarget();
  328. }
  329. ]
  330. ] );
  331. // Test with non-redirect page
  332. $wikitext = false;
  333. $redirectTarget = false;
  334. $content = $this->newContent( 'hello world.' );
  335. $options = ParserOptions::newCanonical( 'canonical' );
  336. $options->setRedirectTarget( $title );
  337. $content->getParserOutput( $title, null, $options );
  338. $this->assertEquals( 'hello world.', $wikitext,
  339. 'Wikitext passed to hook was not as expected'
  340. );
  341. $this->assertEquals( null, $redirectTarget, 'Redirect seen in hook was not null' );
  342. $this->assertEquals( $title, $options->getRedirectTarget(),
  343. 'ParserOptions\' redirectTarget was changed'
  344. );
  345. // Test with a redirect page
  346. $wikitext = false;
  347. $redirectTarget = false;
  348. $content = $this->newContent(
  349. "#REDIRECT [[TestRedirectParserOption/redir]]\nhello redirect."
  350. );
  351. $options = ParserOptions::newCanonical( 'canonical' );
  352. $content->getParserOutput( $title, null, $options );
  353. $this->assertEquals(
  354. 'hello redirect.',
  355. $wikitext,
  356. 'Wikitext passed to hook was not as expected'
  357. );
  358. $this->assertNotEquals(
  359. null,
  360. $redirectTarget,
  361. 'Redirect seen in hook was null' );
  362. $this->assertEquals(
  363. 'TestRedirectParserOption/redir',
  364. $redirectTarget->getFullText(),
  365. 'Redirect seen in hook was not the expected title'
  366. );
  367. $this->assertEquals(
  368. null,
  369. $options->getRedirectTarget(),
  370. 'ParserOptions\' redirectTarget was changed'
  371. );
  372. }
  373. public static function dataEquals() {
  374. return [
  375. [ new WikitextContent( "hallo" ), null, false ],
  376. [ new WikitextContent( "hallo" ), new WikitextContent( "hallo" ), true ],
  377. [ new WikitextContent( "hallo" ), new JavaScriptContent( "hallo" ), false ],
  378. [ new WikitextContent( "hallo" ), new TextContent( "hallo" ), false ],
  379. [ new WikitextContent( "hallo" ), new WikitextContent( "HALLO" ), false ],
  380. ];
  381. }
  382. public static function dataGetDeletionUpdates() {
  383. return [
  384. [
  385. CONTENT_MODEL_WIKITEXT, "hello ''world''\n",
  386. [ LinksDeletionUpdate::class => [] ]
  387. ],
  388. [
  389. CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n",
  390. [ LinksDeletionUpdate::class => [] ]
  391. ],
  392. // @todo more...?
  393. ];
  394. }
  395. /**
  396. * @covers WikitextContent::preSaveTransform
  397. * @covers WikitextContent::fillParserOutput
  398. */
  399. public function testHadSignature() {
  400. $titleObj = Title::newFromText( __CLASS__ );
  401. $content = new WikitextContent( '~~~~' );
  402. $pstContent = $content->preSaveTransform(
  403. $titleObj, $this->getTestUser()->getUser(), new ParserOptions()
  404. );
  405. $this->assertTrue( $pstContent->getParserOutput( $titleObj )->getFlag( 'user-signature' ) );
  406. }
  407. }