Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

macros_test.rb 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006-2019 Jean-Philippe Lang
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. require File.expand_path('../../../../../test_helper', __FILE__)
  19. class Redmine::WikiFormatting::MacrosTest < Redmine::HelperTest
  20. include ApplicationHelper
  21. include ActionView::Helpers::TextHelper
  22. include ActionView::Helpers::SanitizeHelper
  23. include ERB::Util
  24. include Rails.application.routes.url_helpers
  25. extend ActionView::Helpers::SanitizeHelper::ClassMethods
  26. fixtures :projects, :roles, :enabled_modules, :users,
  27. :repositories, :changesets,
  28. :trackers, :issue_statuses, :issues,
  29. :versions, :documents,
  30. :wikis, :wiki_pages, :wiki_contents,
  31. :boards, :messages,
  32. :attachments
  33. def setup
  34. super
  35. @project = nil
  36. end
  37. def teardown
  38. end
  39. def test_macro_registration
  40. Redmine::WikiFormatting::Macros.register do
  41. macro :foo do |obj, args|
  42. "Foo: #{args.size} (#{args.join(',')}) (#{args.class.name})"
  43. end
  44. end
  45. assert_equal '<p>Foo: 0 () (Array)</p>', textilizable("{{foo}}")
  46. assert_equal '<p>Foo: 0 () (Array)</p>', textilizable("{{foo()}}")
  47. assert_equal '<p>Foo: 1 (arg1) (Array)</p>', textilizable("{{foo(arg1)}}")
  48. assert_equal '<p>Foo: 2 (arg1,arg2) (Array)</p>', textilizable("{{foo(arg1, arg2)}}")
  49. end
  50. def test_macro_registration_parse_args_set_to_false_should_disable_arguments_parsing
  51. Redmine::WikiFormatting::Macros.register do
  52. macro :bar, :parse_args => false do |obj, args|
  53. "Bar: (#{args}) (#{args.class.name})"
  54. end
  55. end
  56. assert_equal '<p>Bar: (args, more args) (String)</p>', textilizable("{{bar(args, more args)}}")
  57. assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar}}")
  58. assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar()}}")
  59. end
  60. def test_macro_registration_with_3_args_should_receive_text_argument
  61. Redmine::WikiFormatting::Macros.register do
  62. macro :baz do |obj, args, text|
  63. "Baz: (#{args.join(',')}) (#{text.class.name}) (#{text})"
  64. end
  65. end
  66. assert_equal "<p>Baz: () (NilClass) ()</p>", textilizable("{{baz}}")
  67. assert_equal "<p>Baz: () (NilClass) ()</p>", textilizable("{{baz()}}")
  68. assert_equal "<p>Baz: () (String) (line1\nline2)</p>", textilizable("{{baz()\nline1\nline2\n}}")
  69. assert_equal "<p>Baz: (arg1,arg2) (String) (line1\nline2)</p>", textilizable("{{baz(arg1, arg2)\nline1\nline2\n}}")
  70. end
  71. def test_macro_name_with_upper_case
  72. Redmine::WikiFormatting::Macros.macro(:UpperCase) {|obj, args| "Upper"}
  73. assert_equal "<p>Upper</p>", textilizable("{{UpperCase}}")
  74. end
  75. def test_multiple_macros_on_the_same_line
  76. Redmine::WikiFormatting::Macros.macro :foo do |obj, args|
  77. args.any? ? "args: #{args.join(',')}" : "no args"
  78. end
  79. assert_equal '<p>no args no args</p>', textilizable("{{foo}} {{foo}}")
  80. assert_equal '<p>args: a,b no args</p>', textilizable("{{foo(a,b)}} {{foo}}")
  81. assert_equal '<p>args: a,b args: c,d</p>', textilizable("{{foo(a,b)}} {{foo(c,d)}}")
  82. assert_equal '<p>no args args: c,d</p>', textilizable("{{foo}} {{foo(c,d)}}")
  83. end
  84. def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute
  85. issue = Issue.find(1)
  86. issue.description = "{{hello_world}}"
  87. assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(issue, :description)
  88. end
  89. def test_macro_should_receive_the_object_as_argument_when_called_with_object_option
  90. text = "{{hello_world}}"
  91. assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(text, :object => Issue.find(1))
  92. end
  93. def test_extract_macro_options_should_with_args
  94. options = extract_macro_options(["arg1", "arg2"], :foo, :size)
  95. assert_equal([["arg1", "arg2"], {}], options)
  96. end
  97. def test_extract_macro_options_should_with_options
  98. options = extract_macro_options(["foo=bar", "size=2"], :foo, :size)
  99. assert_equal([[], {:foo => "bar", :size => "2"}], options)
  100. end
  101. def test_extract_macro_options_should_with_args_and_options
  102. options = extract_macro_options(["arg1", "arg2", "foo=bar", "size=2"], :foo, :size)
  103. assert_equal([["arg1", "arg2"], {:foo => "bar", :size => "2"}], options)
  104. end
  105. def test_extract_macro_options_should_parse_options_lazily
  106. options = extract_macro_options(["params=x=1&y=2"], :params)
  107. assert_equal([[], {:params => "x=1&y=2"}], options)
  108. end
  109. def test_macro_exception_should_be_displayed
  110. Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
  111. raise "My message"
  112. end
  113. text = "{{exception}}"
  114. assert_include '<div class="flash error">Error executing the <strong>exception</strong> macro (My message)</div>', textilizable(text)
  115. end
  116. def test_macro_arguments_should_not_be_parsed_by_formatters
  117. text = '{{hello_world(http://www.redmine.org, #1)}}'
  118. assert_include 'Arguments: http://www.redmine.org, #1', textilizable(text)
  119. end
  120. def test_exclamation_mark_should_not_run_macros
  121. text = "!{{hello_world}}"
  122. assert_equal '<p>{{hello_world}}</p>', textilizable(text)
  123. end
  124. def test_exclamation_mark_should_escape_macros
  125. text = "!{{hello_world(<tag>)}}"
  126. assert_equal '<p>{{hello_world(&lt;tag&gt;)}}</p>', textilizable(text)
  127. end
  128. def test_unknown_macros_should_not_be_replaced
  129. text = "{{unknown}}"
  130. assert_equal '<p>{{unknown}}</p>', textilizable(text)
  131. end
  132. def test_unknown_macros_should_parsed_as_text
  133. text = "{{unknown(*test*)}}"
  134. assert_equal '<p>{{unknown(<strong>test</strong>)}}</p>', textilizable(text)
  135. end
  136. def test_unknown_macros_should_be_escaped
  137. text = "{{unknown(<tag>)}}"
  138. assert_equal '<p>{{unknown(&lt;tag&gt;)}}</p>', textilizable(text)
  139. end
  140. def test_html_safe_macro_output_should_not_be_escaped
  141. Redmine::WikiFormatting::Macros.macro :safe_macro do |obj, args|
  142. "<tag>".html_safe
  143. end
  144. assert_equal '<p><tag></p>', textilizable("{{safe_macro}}")
  145. end
  146. def test_macro_hello_world
  147. text = "{{hello_world}}"
  148. assert textilizable(text).match(/Hello world!/)
  149. end
  150. def test_macro_hello_world_should_escape_arguments
  151. text = "{{hello_world(<tag>)}}"
  152. assert_include 'Arguments: &lt;tag&gt;', textilizable(text)
  153. end
  154. def test_macro_macro_list
  155. text = "{{macro_list}}"
  156. assert_match %r{<code>hello_world</code>}, textilizable(text)
  157. end
  158. def test_macro_include
  159. @project = Project.find(1)
  160. # include a page of the current project wiki
  161. text = "{{include(Another page)}}"
  162. assert_include 'This is a link to a ticket', textilizable(text)
  163. @project = nil
  164. # include a page of a specific project wiki
  165. text = "{{include(ecookbook:Another page)}}"
  166. assert_include 'This is a link to a ticket', textilizable(text)
  167. text = "{{include(ecookbook:)}}"
  168. assert_include 'CookBook documentation', textilizable(text)
  169. text = "{{include(unknowidentifier:somepage)}}"
  170. assert_include 'Page not found', textilizable(text)
  171. end
  172. def test_macro_collapse
  173. text = "{{collapse\n*Collapsed* block of text\n}}"
  174. with_locale 'en' do
  175. result = textilizable(text)
  176. assert_select_in result, 'div.collapsed-text'
  177. assert_select_in result, 'strong', :text => 'Collapsed'
  178. assert_select_in result, 'a.collapsible.collapsed', :text => 'Show'
  179. assert_select_in result, 'a.collapsible', :text => 'Hide'
  180. end
  181. end
  182. def test_macro_collapse_with_one_arg
  183. text = "{{collapse(Example)\n*Collapsed* block of text\n}}"
  184. result = textilizable(text)
  185. assert_select_in result, 'div.collapsed-text'
  186. assert_select_in result, 'strong', :text => 'Collapsed'
  187. assert_select_in result, 'a.collapsible.collapsed', :text => 'Example'
  188. assert_select_in result, 'a.collapsible', :text => 'Example'
  189. end
  190. def test_macro_collapse_with_two_args
  191. text = "{{collapse(Show example, Hide example)\n*Collapsed* block of text\n}}"
  192. result = textilizable(text)
  193. assert_select_in result, 'div.collapsed-text'
  194. assert_select_in result, 'strong', :text => 'Collapsed'
  195. assert_select_in result, 'a.collapsible.collapsed', :text => 'Show example'
  196. assert_select_in result, 'a.collapsible', :text => 'Hide example'
  197. end
  198. def test_macro_collapse_should_not_break_toc
  199. set_language_if_valid 'en'
  200. text = <<-RAW
  201. {{toc}}
  202. h1. Title
  203. {{collapse(Show example, Hide example)
  204. h2. Heading
  205. }}"
  206. RAW
  207. expected_toc = '<ul class="toc"><li><strong>Table of contents</strong></li><li><a href="#Title">Title</a><ul><li><a href="#Heading">Heading</a></li></ul></li></ul>'
  208. assert_include expected_toc, textilizable(text).gsub(/[\r\n]/, '')
  209. end
  210. def test_macro_child_pages
  211. expected = "<p><ul class=\"pages-hierarchy\">\n" +
  212. "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a>\n" +
  213. "<ul class=\"pages-hierarchy\">\n<li><a href=\"/projects/ecookbook/wiki/Child_1_1\">Child 1 1</a></li>\n</ul>\n</li>\n" +
  214. "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
  215. "</ul>\n</p>"
  216. @project = Project.find(1)
  217. # child pages of the current wiki page
  218. assert_equal expected, textilizable("{{child_pages}}", :object => WikiPage.find(2).content)
  219. # child pages of another page
  220. assert_equal expected, textilizable("{{child_pages(Another_page)}}", :object => WikiPage.find(1).content)
  221. @project = Project.find(2)
  222. assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page)}}", :object => WikiPage.find(1).content)
  223. end
  224. def test_macro_child_pages_with_parent_option
  225. expected = "<p><ul class=\"pages-hierarchy\">\n" +
  226. "<li><a href=\"/projects/ecookbook/wiki/Another_page\">Another page</a>\n" +
  227. "<ul class=\"pages-hierarchy\">\n" +
  228. "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a>\n" +
  229. "<ul class=\"pages-hierarchy\">\n<li><a href=\"/projects/ecookbook/wiki/Child_1_1\">Child 1 1</a></li>\n</ul>\n</li>\n" +
  230. "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
  231. "</ul>\n</li>\n</ul>\n</p>"
  232. @project = Project.find(1)
  233. # child pages of the current wiki page
  234. assert_equal expected, textilizable("{{child_pages(parent=1)}}", :object => WikiPage.find(2).content)
  235. # child pages of another page
  236. assert_equal expected, textilizable("{{child_pages(Another_page, parent=1)}}", :object => WikiPage.find(1).content)
  237. @project = Project.find(2)
  238. assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page, parent=1)}}", :object => WikiPage.find(1).content)
  239. end
  240. def test_macro_child_pages_with_depth_option
  241. expected = "<p><ul class=\"pages-hierarchy\">\n" +
  242. "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" +
  243. "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
  244. "</ul>\n</p>"
  245. @project = Project.find(1)
  246. assert_equal expected, textilizable("{{child_pages(depth=1)}}", :object => WikiPage.find(2).content)
  247. end
  248. def test_macro_child_pages_without_wiki_page_should_fail
  249. assert_match /can be called from wiki pages only/, textilizable("{{child_pages}}")
  250. end
  251. def test_macro_thumbnail
  252. link = link_to('<img alt="testfile.PNG" src="/attachments/thumbnail/17/200" />'.html_safe,
  253. "/attachments/17",
  254. :class => "thumbnail",
  255. :title => "testfile.PNG")
  256. assert_equal "<p>#{link}</p>",
  257. textilizable("{{thumbnail(testfile.png)}}", :object => Issue.find(14))
  258. end
  259. def test_macro_thumbnail_with_full_path
  260. link = link_to('<img alt="testfile.PNG" src="http://test.host/attachments/thumbnail/17/200" />'.html_safe,
  261. "http://test.host/attachments/17",
  262. :class => "thumbnail",
  263. :title => "testfile.PNG")
  264. assert_equal "<p>#{link}</p>",
  265. textilizable("{{thumbnail(testfile.png)}}", :object => Issue.find(14), :only_path => false)
  266. end
  267. def test_macro_thumbnail_with_size
  268. link = link_to('<img alt="testfile.PNG" src="/attachments/thumbnail/17/400" />'.html_safe,
  269. "/attachments/17",
  270. :class => "thumbnail",
  271. :title => "testfile.PNG")
  272. assert_equal "<p>#{link}</p>",
  273. textilizable("{{thumbnail(testfile.png, size=400)}}", :object => Issue.find(14))
  274. end
  275. def test_macro_thumbnail_with_title
  276. link = link_to('<img alt="testfile.PNG" src="/attachments/thumbnail/17/200" />'.html_safe,
  277. "/attachments/17",
  278. :class => "thumbnail",
  279. :title => "Cool image")
  280. assert_equal "<p>#{link}</p>",
  281. textilizable("{{thumbnail(testfile.png, title=Cool image)}}", :object => Issue.find(14))
  282. end
  283. def test_macro_thumbnail_with_invalid_filename_should_fail
  284. assert_include 'test.png not found',
  285. textilizable("{{thumbnail(test.png)}}", :object => Issue.find(14))
  286. end
  287. def test_macros_should_not_be_executed_in_pre_tags
  288. text = <<-RAW
  289. {{hello_world(foo)}}
  290. <pre>
  291. {{hello_world(pre)}}
  292. !{{hello_world(pre)}}
  293. </pre>
  294. {{hello_world(bar)}}
  295. RAW
  296. expected = <<-EXPECTED
  297. <p>Hello world! Object: NilClass, Arguments: foo and no block of text.</p>
  298. <pre>
  299. {{hello_world(pre)}}
  300. !{{hello_world(pre)}}
  301. </pre>
  302. <p>Hello world! Object: NilClass, Arguments: bar and no block of text.</p>
  303. EXPECTED
  304. assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
  305. end
  306. def test_macros_should_be_escaped_in_pre_tags
  307. text = "<pre>{{hello_world(<tag>)}}</pre>"
  308. assert_equal "<pre>{{hello_world(&lt;tag&gt;)}}</pre>", textilizable(text)
  309. end
  310. def test_macros_should_not_mangle_next_macros_outputs
  311. text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}'
  312. assert_equal '<p>{{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo and no block of text.</p>', textilizable(text)
  313. end
  314. def test_macros_with_text_should_not_mangle_following_macros
  315. text = <<-RAW
  316. {{hello_world
  317. Line of text
  318. }}
  319. {{hello_world
  320. Another line of text
  321. }}
  322. RAW
  323. expected = <<-EXPECTED
  324. <p>Hello world! Object: NilClass, Called with no argument and a 12 bytes long block of text.</p>
  325. <p>Hello world! Object: NilClass, Called with no argument and a 20 bytes long block of text.</p>
  326. EXPECTED
  327. assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
  328. end
  329. def test_macro_should_support_phrase_modifiers
  330. text = "*{{hello_world}}*"
  331. assert_match %r|\A<p><strong>Hello world!.*</strong></p>\z|, textilizable(text)
  332. end
  333. def test_issue_macro_should_not_render_link_if_not_visible
  334. assert_equal "<p>#123</p>", textilizable('{{issue(123)}}')
  335. end
  336. def test_issue_macro_should_render_link_to_issue
  337. issue = Issue.find 1
  338. assert_equal %{<p><a class="issue tracker-1 status-1 priority-4 priority-lowest" href="/issues/1">Bug #1</a>: #{issue.subject}</p>}, textilizable("{{issue(1)}}")
  339. assert_equal %{<p>eCookbook - <a class="issue tracker-1 status-1 priority-4 priority-lowest" href="/issues/1">Bug #1</a>: #{issue.subject}</p>}, textilizable("{{issue(1, project=true)}}")
  340. end
  341. end