You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

textile_formatter_test.rb 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. # frozen_string_literal: true
  2. #
  3. # Redmine - project management software
  4. # Copyright (C) 2006-2019 Jean-Philippe Lang
  5. #
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License
  8. # as published by the Free Software Foundation; either version 2
  9. # of the License, or (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. require File.expand_path('../../../../../test_helper', __FILE__)
  20. require 'digest/md5'
  21. class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase
  22. def setup
  23. @formatter = Redmine::WikiFormatting::Textile::Formatter
  24. end
  25. MODIFIERS = {
  26. "*" => 'strong', # bold
  27. "_" => 'em', # italic
  28. "+" => 'ins', # underline
  29. "-" => 'del', # deleted
  30. "^" => 'sup', # superscript
  31. "~" => 'sub' # subscript
  32. }
  33. def test_modifiers
  34. assert_html_output(
  35. '*bold*' => '<strong>bold</strong>',
  36. 'before *bold*' => 'before <strong>bold</strong>',
  37. '*bold* after' => '<strong>bold</strong> after',
  38. '*two words*' => '<strong>two words</strong>',
  39. '*two*words*' => '<strong>two*words</strong>',
  40. '*two * words*' => '<strong>two * words</strong>',
  41. '*two* *words*' => '<strong>two</strong> <strong>words</strong>',
  42. '*(two)* *(words)*' => '<strong>(two)</strong> <strong>(words)</strong>'
  43. )
  44. end
  45. def test_modifiers_combination
  46. MODIFIERS.each do |m1, tag1|
  47. MODIFIERS.each do |m2, tag2|
  48. next if m1 == m2
  49. text = "#{m2}#{m1}Phrase modifiers#{m1}#{m2}"
  50. html = "<#{tag2}><#{tag1}>Phrase modifiers</#{tag1}></#{tag2}>"
  51. assert_html_output text => html
  52. end
  53. end
  54. end
  55. def test_modifier_should_work_with_one_non_ascii_character
  56. assert_html_output "*Ä*" => "<strong>Ä</strong>"
  57. end
  58. def test_styles
  59. # single style
  60. assert_html_output({
  61. 'p{color:red}. text' => '<p style="color:red;">text</p>',
  62. 'p{color:red;}. text' => '<p style="color:red;">text</p>',
  63. 'p{color: red}. text' => '<p style="color: red;">text</p>',
  64. 'p{color:#f00}. text' => '<p style="color:#f00;">text</p>',
  65. 'p{color:#ff0000}. text' => '<p style="color:#ff0000;">text</p>',
  66. 'p{border:10px}. text' => '<p style="border:10px;">text</p>',
  67. 'p{border:10}. text' => '<p style="border:10;">text</p>',
  68. 'p{border:10%}. text' => '<p style="border:10%;">text</p>',
  69. 'p{border:10em}. text' => '<p style="border:10em;">text</p>',
  70. 'p{border:1.5em}. text' => '<p style="border:1.5em;">text</p>',
  71. 'p{border-left:1px}. text' => '<p style="border-left:1px;">text</p>',
  72. 'p{border-right:1px}. text' => '<p style="border-right:1px;">text</p>',
  73. 'p{border-top:1px}. text' => '<p style="border-top:1px;">text</p>',
  74. 'p{border-bottom:1px}. text' => '<p style="border-bottom:1px;">text</p>',
  75. 'p{width:50px}. text' => '<p style="width:50px;">text</p>',
  76. 'p{max-width:100px}. text' => '<p style="max-width:100px;">text</p>',
  77. 'p{height:40px}. text' => '<p style="height:40px;">text</p>',
  78. 'p{max-height:80px}. text' => '<p style="max-height:80px;">text</p>',
  79. }, false)
  80. # multiple styles
  81. assert_html_output({
  82. 'p{color:red; border-top:1px}. text' => '<p style="color:red;border-top:1px;">text</p>',
  83. 'p{color:red ; border-top:1px}. text' => '<p style="color:red;border-top:1px;">text</p>',
  84. 'p{color:red;border-top:1px}. text' => '<p style="color:red;border-top:1px;">text</p>',
  85. }, false)
  86. # styles with multiple values
  87. assert_html_output({
  88. 'p{border:1px solid red;}. text' => '<p style="border:1px solid red;">text</p>',
  89. 'p{border-top-left-radius: 10px 5px;}. text' => '<p style="border-top-left-radius: 10px 5px;">text</p>',
  90. }, false)
  91. end
  92. def test_invalid_styles_should_be_filtered
  93. assert_html_output({
  94. 'p{invalid}. text' => '<p>text</p>',
  95. 'p{invalid:red}. text' => '<p>text</p>',
  96. 'p{color:(red)}. text' => '<p>text</p>',
  97. 'p{color:red;invalid:blue}. text' => '<p style="color:red;">text</p>',
  98. 'p{invalid:blue;color:red}. text' => '<p style="color:red;">text</p>',
  99. 'p{color:"}. text' => '<p>p{color:"}. text</p>',
  100. }, false)
  101. end
  102. def test_inline_code
  103. assert_html_output(
  104. 'this is @some code@' => 'this is <code>some code</code>',
  105. '@<Location /redmine>@' => '<code>&lt;Location /redmine&gt;</code>'
  106. )
  107. end
  108. def test_lang_attribute
  109. assert_html_output(
  110. '*[fr]French*' => '<strong lang="fr">French</strong>',
  111. '*[fr-fr]French*' => '<strong lang="fr-fr">French</strong>',
  112. '*[fr_fr]French*' => '<strong lang="fr_fr">French</strong>'
  113. )
  114. end
  115. def test_lang_attribute_should_ignore_invalid_value
  116. assert_html_output(
  117. '*[fr3]French*' => '<strong>[fr3]French</strong>'
  118. )
  119. end
  120. def test_nested_lists
  121. raw = <<-RAW
  122. # Item 1
  123. # Item 2
  124. ** Item 2a
  125. ** Item 2b
  126. # Item 3
  127. ** Item 3a
  128. RAW
  129. expected = <<-EXPECTED
  130. <ol>
  131. <li>Item 1</li>
  132. <li>Item 2
  133. <ul>
  134. <li>Item 2a</li>
  135. <li>Item 2b</li>
  136. </ul>
  137. </li>
  138. <li>Item 3
  139. <ul>
  140. <li>Item 3a</li>
  141. </ul>
  142. </li>
  143. </ol>
  144. EXPECTED
  145. assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
  146. end
  147. def test_escaping
  148. assert_html_output(
  149. 'this is a <script>' => 'this is a &lt;script&gt;'
  150. )
  151. end
  152. def test_kbd
  153. assert_html_output({
  154. '<kbd>test</kbd>' => '<kbd>test</kbd>'
  155. }, false)
  156. end
  157. def test_use_of_backslashes_followed_by_numbers_in_headers
  158. assert_html_output({
  159. 'h1. 2009\02\09' => '<h1>2009\02\09</h1>'
  160. }, false)
  161. end
  162. def test_double_dashes_should_not_strikethrough
  163. assert_html_output(
  164. 'double -- dashes -- test' => 'double -- dashes -- test',
  165. 'double -- *dashes* -- test' => 'double -- <strong>dashes</strong> -- test'
  166. )
  167. end
  168. def test_abbreviations
  169. assert_html_output(
  170. 'this is an abbreviation: GPL(General Public License)' => 'this is an abbreviation: <abbr title="General Public License">GPL</abbr>',
  171. '2 letters JP(Jean-Philippe) abbreviation' => '2 letters <abbr title="Jean-Philippe">JP</abbr> abbreviation',
  172. 'GPL(This is a double-quoted "title")' => '<abbr title="This is a double-quoted &quot;title&quot;">GPL</abbr>'
  173. )
  174. end
  175. def test_blockquote
  176. # orig raw text
  177. raw = <<-RAW
  178. John said:
  179. > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
  180. > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
  181. > * Donec odio lorem,
  182. > * sagittis ac,
  183. > * malesuada in,
  184. > * adipiscing eu, dolor.
  185. >
  186. > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
  187. > Proin a tellus. Nam vel neque.
  188. He's right.
  189. RAW
  190. # expected html
  191. expected = <<-EXPECTED
  192. <p>John said:</p>
  193. <blockquote>
  194. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.<br />
  195. Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
  196. <ul>
  197. <li>Donec odio lorem,</li>
  198. <li>sagittis ac,</li>
  199. <li>malesuada in,</li>
  200. <li>adipiscing eu, dolor.</li>
  201. </ul>
  202. <blockquote>
  203. <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
  204. </blockquote>
  205. <p>Proin a tellus. Nam vel neque.</p>
  206. </blockquote>
  207. <p>He's right.</p>
  208. EXPECTED
  209. assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
  210. end
  211. def test_table
  212. raw = <<-RAW
  213. This is a table with empty cells:
  214. |cell11|cell12||
  215. |cell21||cell23|
  216. |cell31|cell32|cell33|
  217. RAW
  218. expected = <<-EXPECTED
  219. <p>This is a table with empty cells:</p>
  220. <table>
  221. <tr><td>cell11</td><td>cell12</td><td></td></tr>
  222. <tr><td>cell21</td><td></td><td>cell23</td></tr>
  223. <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
  224. </table>
  225. EXPECTED
  226. assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
  227. end
  228. def test_table_with_alignment
  229. raw = <<-RAW
  230. |>. right|
  231. |<. left|
  232. |<>. justify|
  233. RAW
  234. expected = <<-EXPECTED
  235. <table>
  236. <tr><td style="text-align:right;">right</td></tr>
  237. <tr><td style="text-align:left;">left</td></tr>
  238. <tr><td style="text-align:justify;">justify</td></tr>
  239. </table>
  240. EXPECTED
  241. assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
  242. end
  243. def test_table_with_trailing_whitespace
  244. raw = <<-RAW
  245. This is a table with trailing whitespace in one row:
  246. |cell11|cell12|
  247. |cell21|cell22|
  248. |cell31|cell32|
  249. RAW
  250. expected = <<-EXPECTED
  251. <p>This is a table with trailing whitespace in one row:</p>
  252. <table>
  253. <tr><td>cell11</td><td>cell12</td></tr>
  254. <tr><td>cell21</td><td>cell22</td></tr>
  255. <tr><td>cell31</td><td>cell32</td></tr>
  256. </table>
  257. EXPECTED
  258. assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
  259. end
  260. def test_table_with_line_breaks
  261. raw = <<-RAW
  262. This is a table with line breaks:
  263. |cell11
  264. continued|cell12||
  265. |-cell21-||cell23
  266. cell23 line2
  267. cell23 *line3*|
  268. |cell31|cell32
  269. cell32 line2|cell33|
  270. RAW
  271. expected = <<-EXPECTED
  272. <p>This is a table with line breaks:</p>
  273. <table>
  274. <tr>
  275. <td>cell11<br />continued</td>
  276. <td>cell12</td>
  277. <td></td>
  278. </tr>
  279. <tr>
  280. <td><del>cell21</del></td>
  281. <td></td>
  282. <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
  283. </tr>
  284. <tr>
  285. <td>cell31</td>
  286. <td>cell32<br/>cell32 line2</td>
  287. <td>cell33</td>
  288. </tr>
  289. </table>
  290. EXPECTED
  291. assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
  292. end
  293. def test_tables_with_lists
  294. raw = <<-RAW
  295. This is a table with lists:
  296. |cell11|cell12|
  297. |cell21|ordered list
  298. # item
  299. # item 2|
  300. |cell31|unordered list
  301. * item
  302. * item 2|
  303. RAW
  304. expected = <<-EXPECTED
  305. <p>This is a table with lists:</p>
  306. <table>
  307. <tr>
  308. <td>cell11</td>
  309. <td>cell12</td>
  310. </tr>
  311. <tr>
  312. <td>cell21</td>
  313. <td>ordered list<br /># item<br /># item 2</td>
  314. </tr>
  315. <tr>
  316. <td>cell31</td>
  317. <td>unordered list<br />* item<br />* item 2</td>
  318. </tr>
  319. </table>
  320. EXPECTED
  321. assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
  322. end
  323. def test_textile_should_not_mangle_brackets
  324. assert_equal '<p>[msg1][msg2]</p>', to_html('[msg1][msg2]')
  325. end
  326. def test_textile_should_escape_image_urls
  327. # this is onclick="alert('XSS');" in encoded form
  328. raw = '!/images/comment.png"onclick=&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x27;&#x58;&#x53;&#x53;&#x27;&#x29;;&#x22;!'
  329. expected = '<p><img src="/images/comment.png&quot;onclick=&amp;#x61;&amp;#x6c;&amp;#x65;&amp;#x72;&amp;#x74;&amp;#x28;&amp;#x27;&amp;#x58;&amp;#x53;&amp;#x53;&amp;#x27;&amp;#x29;;&amp;#x22;" alt="" /></p>'
  330. assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
  331. end
  332. STR_WITHOUT_PRE = [
  333. # 0
  334. "h1. Title
  335. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
  336. # 1
  337. "h2. Heading 2
  338. Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
  339. Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.",
  340. # 2
  341. "h2. Heading 2
  342. Morbi facilisis accumsan orci non pharetra.
  343. h3. Heading 3
  344. Nulla nunc nisi, egestas in ornare vel, posuere ac libero.",
  345. # 3
  346. "h3. Heading 3
  347. Praesent eget turpis nibh, a lacinia nulla.",
  348. # 4
  349. "h2. Heading 2
  350. Ut rhoncus elementum adipiscing."]
  351. TEXT_WITHOUT_PRE = STR_WITHOUT_PRE.join("\n\n").freeze
  352. def test_get_section_should_return_the_requested_section_and_its_hash
  353. assert_section_with_hash STR_WITHOUT_PRE[1], TEXT_WITHOUT_PRE, 2
  354. assert_section_with_hash STR_WITHOUT_PRE[2..3].join("\n\n"), TEXT_WITHOUT_PRE, 3
  355. assert_section_with_hash STR_WITHOUT_PRE[3], TEXT_WITHOUT_PRE, 5
  356. assert_section_with_hash STR_WITHOUT_PRE[4], TEXT_WITHOUT_PRE, 6
  357. assert_section_with_hash '', TEXT_WITHOUT_PRE, 0
  358. assert_section_with_hash '', TEXT_WITHOUT_PRE, 10
  359. end
  360. def test_update_section_should_update_the_requested_section
  361. replacement = "New text"
  362. assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement)
  363. assert_equal [STR_WITHOUT_PRE[0..1], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(3, replacement)
  364. assert_equal [STR_WITHOUT_PRE[0..2], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(5, replacement)
  365. assert_equal [STR_WITHOUT_PRE[0..3], replacement].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(6, replacement)
  366. assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(0, replacement)
  367. assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(10, replacement)
  368. end
  369. def test_update_section_with_hash_should_update_the_requested_section
  370. replacement = "New text"
  371. assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"),
  372. @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement, Digest::MD5.hexdigest(STR_WITHOUT_PRE[1]))
  373. end
  374. def test_update_section_with_wrong_hash_should_raise_an_error
  375. assert_raise Redmine::WikiFormatting::StaleSectionError do
  376. @formatter.new(TEXT_WITHOUT_PRE).update_section(2, "New text", Digest::MD5.hexdigest("Old text"))
  377. end
  378. end
  379. STR_WITH_PRE = [
  380. # 0
  381. "h1. Title
  382. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
  383. # 1
  384. "h2. Heading 2
  385. <pre><code class=\"ruby\">
  386. def foo
  387. end
  388. </code></pre>
  389. <pre><code><pre><code class=\"ruby\">
  390. Place your code here.
  391. </code></pre>
  392. </code></pre>
  393. Morbi facilisis accumsan orci non pharetra.
  394. <pre>
  395. Pre Content:
  396. h2. Inside pre
  397. <tag> inside pre block
  398. Morbi facilisis accumsan orci non pharetra.
  399. </pre>",
  400. # 2
  401. "h3. Heading 3
  402. Nulla nunc nisi, egestas in ornare vel, posuere ac libero."]
  403. def test_get_section_should_ignore_pre_content
  404. text = STR_WITH_PRE.join("\n\n")
  405. assert_section_with_hash STR_WITH_PRE[1..2].join("\n\n"), text, 2
  406. assert_section_with_hash STR_WITH_PRE[2], text, 3
  407. end
  408. def test_update_section_should_not_escape_pre_content_outside_section
  409. text = STR_WITH_PRE.join("\n\n")
  410. replacement = "New text"
  411. assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
  412. @formatter.new(text).update_section(3, replacement)
  413. end
  414. def test_get_section_should_support_lines_with_spaces_before_heading
  415. # the lines after Content 2 and Heading 4 contain a space
  416. text = <<-STR
  417. h1. Heading 1
  418. Content 1
  419. h1. Heading 2
  420. Content 2
  421. h1. Heading 3
  422. Content 3
  423. h1. Heading 4
  424. Content 4
  425. STR
  426. [1, 2, 3, 4].each do |index|
  427. assert_match /\Ah1. Heading #{index}.+Content #{index}/m, @formatter.new(text).get_section(index).first
  428. end
  429. end
  430. def test_get_section_should_support_headings_starting_with_a_tab
  431. text = <<-STR
  432. h1.\tHeading 1
  433. Content 1
  434. h1. Heading 2
  435. Content 2
  436. STR
  437. assert_match /\Ah1.\tHeading 1\s+Content 1\z/, @formatter.new(text).get_section(1).first
  438. end
  439. def test_should_not_allow_arbitrary_class_attribute_on_offtags
  440. %w(code pre kbd).each do |tag|
  441. assert_html_output({"<#{tag} class=\"foo\">test</#{tag}>" => "<#{tag}>test</#{tag}>"}, false)
  442. end
  443. assert_html_output({"<notextile class=\"foo\">test</notextile>" => "test"}, false)
  444. end
  445. def test_should_allow_valid_language_class_attribute_on_code_tags
  446. # language name is double-quoted
  447. assert_html_output({"<code class=\"ruby\">test</code>" => "<code class=\"ruby syntaxhl\"><span class=\"nb\">test</span></code>"}, false)
  448. # language name is single-quoted
  449. assert_html_output({"<code class='ruby'>test</code>" => "<code class=\"ruby syntaxhl\"><span class=\"nb\">test</span></code>"}, false)
  450. end
  451. def test_should_not_allow_valid_language_class_attribute_on_non_code_offtags
  452. %w(pre kbd).each do |tag|
  453. assert_html_output({"<#{tag} class=\"ruby\">test</#{tag}>" => "<#{tag}>test</#{tag}>"}, false)
  454. end
  455. assert_html_output({"<notextile class=\"ruby\">test</notextile>" => "test"}, false)
  456. end
  457. def test_should_prefix_class_attribute_on_tags
  458. assert_html_output({
  459. '!(foo)test.png!' => "<p><img src=\"test.png\" class=\"wiki-class-foo\" alt=\"\" /></p>",
  460. '%(foo)test%' => "<p><span class=\"wiki-class-foo\">test</span></p>",
  461. 'p(foo). test' => "<p class=\"wiki-class-foo\">test</p>",
  462. '|(foo). test|' => "<table>\n\t\t<tr>\n\t\t\t<td class=\"wiki-class-foo\">test</td>\n\t\t</tr>\n\t</table>",
  463. }, false)
  464. end
  465. def test_should_prefix_id_attribute_on_tags
  466. assert_html_output({
  467. '!(#foo)test.png!' => "<p><img src=\"test.png\" id=\"wiki-id-foo\" alt=\"\" /></p>",
  468. '%(#foo)test%' => "<p><span id=\"wiki-id-foo\">test</span></p>",
  469. 'p(#foo). test' => "<p id=\"wiki-id-foo\">test</p>",
  470. '|(#foo). test|' => "<table>\n\t\t<tr>\n\t\t\t<td id=\"wiki-id-foo\">test</td>\n\t\t</tr>\n\t</table>",
  471. }, false)
  472. end
  473. def test_should_not_prefix_class_and_id_attributes_already_prefixed
  474. assert_html_output({
  475. '!(wiki-class-foo#wiki-id-bar)test.png!' => "<p><img src=\"test.png\" class=\"wiki-class-foo\" id=\"wiki-id-bar\" alt=\"\" /></p>",
  476. }, false)
  477. end
  478. def test_footnotes
  479. text = <<-STR
  480. This is some text[1].
  481. fn1. This is the foot note
  482. STR
  483. expected = <<-EXPECTED
  484. <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
  485. <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
  486. EXPECTED
  487. assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '')
  488. end
  489. # TODO: Remove this test after migrating to RedCloth 4
  490. def test_should_not_crash_with_special_input
  491. assert_nothing_raised { to_html(" \f") }
  492. assert_nothing_raised { to_html(" \v") }
  493. end
  494. def test_should_not_handle_as_preformatted_text_tags_that_starts_with_pre
  495. text = <<-STR
  496. <pree>
  497. This is some text
  498. </pree>
  499. STR
  500. expected = <<-EXPECTED
  501. <p>&lt;pree&gt;<br />
  502. This is some text<br />
  503. &lt;/pree&gt;</p>
  504. EXPECTED
  505. assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '')
  506. end
  507. private
  508. def assert_html_output(to_test, expect_paragraph = true)
  509. to_test.each do |text, expected|
  510. assert_equal(( expect_paragraph ? "<p>#{expected}</p>" : expected ), @formatter.new(text).to_html, "Formatting the following text failed:\n===\n#{text}\n===\n")
  511. end
  512. end
  513. def to_html(text)
  514. @formatter.new(text).to_html
  515. end
  516. def assert_section_with_hash(expected, text, index)
  517. result = @formatter.new(text).get_section(index)
  518. assert_kind_of Array, result
  519. assert_equal 2, result.size
  520. assert_equal expected, result.first, "section content did not match"
  521. assert_equal Digest::MD5.hexdigest(expected), result.last, "section hash did not match"
  522. end
  523. end