##// END OF EJS Templates
Merged r12899 (#16077)....
Jean-Philippe Lang -
r12644:01aa2db0af8b
parent child
Show More
@@ -1,247 +1,247
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module Redmine
18 module Redmine
19 module WikiFormatting
19 module WikiFormatting
20 module Macros
20 module Macros
21 module Definitions
21 module Definitions
22 # Returns true if +name+ is the name of an existing macro
22 # Returns true if +name+ is the name of an existing macro
23 def macro_exists?(name)
23 def macro_exists?(name)
24 Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym)
24 Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym)
25 end
25 end
26
26
27 def exec_macro(name, obj, args, text)
27 def exec_macro(name, obj, args, text)
28 macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym]
28 macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym]
29 return unless macro_options
29 return unless macro_options
30
30
31 method_name = "macro_#{name}"
31 method_name = "macro_#{name}"
32 unless macro_options[:parse_args] == false
32 unless macro_options[:parse_args] == false
33 args = args.split(',').map(&:strip)
33 args = args.split(',').map(&:strip)
34 end
34 end
35
35
36 begin
36 begin
37 if self.class.instance_method(method_name).arity == 3
37 if self.class.instance_method(method_name).arity == 3
38 send(method_name, obj, args, text)
38 send(method_name, obj, args, text)
39 elsif text
39 elsif text
40 raise "This macro does not accept a block of text"
40 raise "This macro does not accept a block of text"
41 else
41 else
42 send(method_name, obj, args)
42 send(method_name, obj, args)
43 end
43 end
44 rescue => e
44 rescue => e
45 "<div class=\"flash error\">Error executing the <strong>#{h name}</strong> macro (#{h e.to_s})</div>".html_safe
45 "<div class=\"flash error\">Error executing the <strong>#{h name}</strong> macro (#{h e.to_s})</div>".html_safe
46 end
46 end
47 end
47 end
48
48
49 def extract_macro_options(args, *keys)
49 def extract_macro_options(args, *keys)
50 options = {}
50 options = {}
51 while args.last.to_s.strip =~ %r{^(.+?)\=(.+)$} && keys.include?($1.downcase.to_sym)
51 while args.last.to_s.strip =~ %r{^(.+?)\=(.+)$} && keys.include?($1.downcase.to_sym)
52 options[$1.downcase.to_sym] = $2
52 options[$1.downcase.to_sym] = $2
53 args.pop
53 args.pop
54 end
54 end
55 return [args, options]
55 return [args, options]
56 end
56 end
57 end
57 end
58
58
59 @@available_macros = {}
59 @@available_macros = {}
60 mattr_accessor :available_macros
60 mattr_accessor :available_macros
61
61
62 class << self
62 class << self
63 # Plugins can use this method to define new macros:
63 # Plugins can use this method to define new macros:
64 #
64 #
65 # Redmine::WikiFormatting::Macros.register do
65 # Redmine::WikiFormatting::Macros.register do
66 # desc "This is my macro"
66 # desc "This is my macro"
67 # macro :my_macro do |obj, args|
67 # macro :my_macro do |obj, args|
68 # "My macro output"
68 # "My macro output"
69 # end
69 # end
70 #
70 #
71 # desc "This is my macro that accepts a block of text"
71 # desc "This is my macro that accepts a block of text"
72 # macro :my_macro do |obj, args, text|
72 # macro :my_macro do |obj, args, text|
73 # "My macro output"
73 # "My macro output"
74 # end
74 # end
75 # end
75 # end
76 def register(&block)
76 def register(&block)
77 class_eval(&block) if block_given?
77 class_eval(&block) if block_given?
78 end
78 end
79
79
80 # Defines a new macro with the given name, options and block.
80 # Defines a new macro with the given name, options and block.
81 #
81 #
82 # Options:
82 # Options:
83 # * :desc - A description of the macro
83 # * :desc - A description of the macro
84 # * :parse_args => false - Disables arguments parsing (the whole arguments
84 # * :parse_args => false - Disables arguments parsing (the whole arguments
85 # string is passed to the macro)
85 # string is passed to the macro)
86 #
86 #
87 # Macro blocks accept 2 or 3 arguments:
87 # Macro blocks accept 2 or 3 arguments:
88 # * obj: the object that is rendered (eg. an Issue, a WikiContent...)
88 # * obj: the object that is rendered (eg. an Issue, a WikiContent...)
89 # * args: macro arguments
89 # * args: macro arguments
90 # * text: the block of text given to the macro (should be present only if the
90 # * text: the block of text given to the macro (should be present only if the
91 # macro accepts a block of text). text is a String or nil if the macro is
91 # macro accepts a block of text). text is a String or nil if the macro is
92 # invoked without a block of text.
92 # invoked without a block of text.
93 #
93 #
94 # Examples:
94 # Examples:
95 # By default, when the macro is invoked, the coma separated list of arguments
95 # By default, when the macro is invoked, the coma separated list of arguments
96 # is split and passed to the macro block as an array. If no argument is given
96 # is split and passed to the macro block as an array. If no argument is given
97 # the macro will be invoked with an empty array:
97 # the macro will be invoked with an empty array:
98 #
98 #
99 # macro :my_macro do |obj, args|
99 # macro :my_macro do |obj, args|
100 # # args is an array
100 # # args is an array
101 # # and this macro do not accept a block of text
101 # # and this macro do not accept a block of text
102 # end
102 # end
103 #
103 #
104 # You can disable arguments spliting with the :parse_args => false option. In
104 # You can disable arguments spliting with the :parse_args => false option. In
105 # this case, the full string of arguments is passed to the macro:
105 # this case, the full string of arguments is passed to the macro:
106 #
106 #
107 # macro :my_macro, :parse_args => false do |obj, args|
107 # macro :my_macro, :parse_args => false do |obj, args|
108 # # args is a string
108 # # args is a string
109 # end
109 # end
110 #
110 #
111 # Macro can optionally accept a block of text:
111 # Macro can optionally accept a block of text:
112 #
112 #
113 # macro :my_macro do |obj, args, text|
113 # macro :my_macro do |obj, args, text|
114 # # this macro accepts a block of text
114 # # this macro accepts a block of text
115 # end
115 # end
116 #
116 #
117 # Macros are invoked in formatted text using double curly brackets. Arguments
117 # Macros are invoked in formatted text using double curly brackets. Arguments
118 # must be enclosed in parenthesis if any. A new line after the macro name or the
118 # must be enclosed in parenthesis if any. A new line after the macro name or the
119 # arguments starts the block of text that will be passe to the macro (invoking
119 # arguments starts the block of text that will be passe to the macro (invoking
120 # a macro that do not accept a block of text with some text will fail).
120 # a macro that do not accept a block of text with some text will fail).
121 # Examples:
121 # Examples:
122 #
122 #
123 # No arguments:
123 # No arguments:
124 # {{my_macro}}
124 # {{my_macro}}
125 #
125 #
126 # With arguments:
126 # With arguments:
127 # {{my_macro(arg1, arg2)}}
127 # {{my_macro(arg1, arg2)}}
128 #
128 #
129 # With a block of text:
129 # With a block of text:
130 # {{my_macro
130 # {{my_macro
131 # multiple lines
131 # multiple lines
132 # of text
132 # of text
133 # }}
133 # }}
134 #
134 #
135 # With arguments and a block of text
135 # With arguments and a block of text
136 # {{my_macro(arg1, arg2)
136 # {{my_macro(arg1, arg2)
137 # multiple lines
137 # multiple lines
138 # of text
138 # of text
139 # }}
139 # }}
140 #
140 #
141 # If a block of text is given, the closing tag }} must be at the start of a new line.
141 # If a block of text is given, the closing tag }} must be at the start of a new line.
142 def macro(name, options={}, &block)
142 def macro(name, options={}, &block)
143 options.assert_valid_keys(:desc, :parse_args)
143 options.assert_valid_keys(:desc, :parse_args)
144 unless name.to_s.match(/\A\w+\z/)
144 unless name.to_s.match(/\A\w+\z/)
145 raise "Invalid macro name: #{name} (only 0-9, A-Z, a-z and _ characters are accepted)"
145 raise "Invalid macro name: #{name} (only 0-9, A-Z, a-z and _ characters are accepted)"
146 end
146 end
147 unless block_given?
147 unless block_given?
148 raise "Can not create a macro without a block!"
148 raise "Can not create a macro without a block!"
149 end
149 end
150 name = name.to_s.downcase.to_sym
150 name = name.to_s.downcase.to_sym
151 available_macros[name] = {:desc => @@desc || ''}.merge(options)
151 available_macros[name] = {:desc => @@desc || ''}.merge(options)
152 @@desc = nil
152 @@desc = nil
153 Definitions.send :define_method, "macro_#{name}", &block
153 Definitions.send :define_method, "macro_#{name}", &block
154 end
154 end
155
155
156 # Sets description for the next macro to be defined
156 # Sets description for the next macro to be defined
157 def desc(txt)
157 def desc(txt)
158 @@desc = txt
158 @@desc = txt
159 end
159 end
160 end
160 end
161
161
162 # Builtin macros
162 # Builtin macros
163 desc "Sample macro."
163 desc "Sample macro."
164 macro :hello_world do |obj, args, text|
164 macro :hello_world do |obj, args, text|
165 h("Hello world! Object: #{obj.class.name}, " +
165 h("Hello world! Object: #{obj.class.name}, " +
166 (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") +
166 (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") +
167 " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.")
167 " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.")
168 )
168 )
169 end
169 end
170
170
171 desc "Displays a list of all available macros, including description if available."
171 desc "Displays a list of all available macros, including description if available."
172 macro :macro_list do |obj, args|
172 macro :macro_list do |obj, args|
173 out = ''.html_safe
173 out = ''.html_safe
174 @@available_macros.each do |macro, options|
174 @@available_macros.each do |macro, options|
175 out << content_tag('dt', content_tag('code', macro.to_s))
175 out << content_tag('dt', content_tag('code', macro.to_s))
176 out << content_tag('dd', textilizable(options[:desc]))
176 out << content_tag('dd', textilizable(options[:desc]))
177 end
177 end
178 content_tag('dl', out)
178 content_tag('dl', out)
179 end
179 end
180
180
181 desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" +
181 desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" +
182 " !{{child_pages}} -- can be used from a wiki page only\n" +
182 " !{{child_pages}} -- can be used from a wiki page only\n" +
183 " !{{child_pages(depth=2)}} -- display 2 levels nesting only\n"
183 " !{{child_pages(depth=2)}} -- display 2 levels nesting only\n"
184 " !{{child_pages(Foo)}} -- lists all children of page Foo\n" +
184 " !{{child_pages(Foo)}} -- lists all children of page Foo\n" +
185 " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo"
185 " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo"
186 macro :child_pages do |obj, args|
186 macro :child_pages do |obj, args|
187 args, options = extract_macro_options(args, :parent, :depth)
187 args, options = extract_macro_options(args, :parent, :depth)
188 options[:depth] = options[:depth].to_i if options[:depth].present?
188 options[:depth] = options[:depth].to_i if options[:depth].present?
189
189
190 page = nil
190 page = nil
191 if args.size > 0
191 if args.size > 0
192 page = Wiki.find_page(args.first.to_s, :project => @project)
192 page = Wiki.find_page(args.first.to_s, :project => @project)
193 elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)
193 elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)
194 page = obj.page
194 page = obj.page
195 else
195 else
196 raise 'With no argument, this macro can be called from wiki pages only.'
196 raise 'With no argument, this macro can be called from wiki pages only.'
197 end
197 end
198 raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
198 raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
199 pages = page.self_and_descendants(options[:depth]).group_by(&:parent_id)
199 pages = page.self_and_descendants(options[:depth]).group_by(&:parent_id)
200 render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id)
200 render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id)
201 end
201 end
202
202
203 desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}"
203 desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}"
204 macro :include do |obj, args|
204 macro :include do |obj, args|
205 page = Wiki.find_page(args.first.to_s, :project => @project)
205 page = Wiki.find_page(args.first.to_s, :project => @project)
206 raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
206 raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
207 @included_wiki_pages ||= []
207 @included_wiki_pages ||= []
208 raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title)
208 raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title)
209 @included_wiki_pages << page.title
209 @included_wiki_pages << page.title
210 out = textilizable(page.content, :text, :attachments => page.attachments, :headings => false)
210 out = textilizable(page.content, :text, :attachments => page.attachments, :headings => false)
211 @included_wiki_pages.pop
211 @included_wiki_pages.pop
212 out
212 out
213 end
213 end
214
214
215 desc "Inserts of collapsed block of text. Example:\n\n {{collapse(View details...)\nThis is a block of text that is collapsed by default.\nIt can be expanded by clicking a link.\n}}"
215 desc "Inserts of collapsed block of text. Example:\n\n {{collapse(View details...)\nThis is a block of text that is collapsed by default.\nIt can be expanded by clicking a link.\n}}"
216 macro :collapse do |obj, args, text|
216 macro :collapse do |obj, args, text|
217 html_id = "collapse-#{Redmine::Utils.random_hex(4)}"
217 html_id = "collapse-#{Redmine::Utils.random_hex(4)}"
218 show_label = args[0] || l(:button_show)
218 show_label = args[0] || l(:button_show)
219 hide_label = args[1] || args[0] || l(:button_hide)
219 hide_label = args[1] || args[0] || l(:button_hide)
220 js = "$('##{html_id}-show, ##{html_id}-hide').toggle(); $('##{html_id}').fadeToggle(150);"
220 js = "$('##{html_id}-show, ##{html_id}-hide').toggle(); $('##{html_id}').fadeToggle(150);"
221 out = ''.html_safe
221 out = ''.html_safe
222 out << link_to_function(show_label, js, :id => "#{html_id}-show", :class => 'collapsible collapsed')
222 out << link_to_function(show_label, js, :id => "#{html_id}-show", :class => 'collapsible collapsed')
223 out << link_to_function(hide_label, js, :id => "#{html_id}-hide", :class => 'collapsible', :style => 'display:none;')
223 out << link_to_function(hide_label, js, :id => "#{html_id}-hide", :class => 'collapsible', :style => 'display:none;')
224 out << content_tag('div', textilizable(text, :object => obj), :id => html_id, :class => 'collapsed-text', :style => 'display:none;')
224 out << content_tag('div', textilizable(text, :object => obj, :headings => false), :id => html_id, :class => 'collapsed-text', :style => 'display:none;')
225 out
225 out
226 end
226 end
227
227
228 desc "Displays a clickable thumbnail of an attached image. Examples:\n\n<pre>{{thumbnail(image.png)}}\n{{thumbnail(image.png, size=300, title=Thumbnail)}}</pre>"
228 desc "Displays a clickable thumbnail of an attached image. Examples:\n\n<pre>{{thumbnail(image.png)}}\n{{thumbnail(image.png, size=300, title=Thumbnail)}}</pre>"
229 macro :thumbnail do |obj, args|
229 macro :thumbnail do |obj, args|
230 args, options = extract_macro_options(args, :size, :title)
230 args, options = extract_macro_options(args, :size, :title)
231 filename = args.first
231 filename = args.first
232 raise 'Filename required' unless filename.present?
232 raise 'Filename required' unless filename.present?
233 size = options[:size]
233 size = options[:size]
234 raise 'Invalid size parameter' unless size.nil? || size.match(/^\d+$/)
234 raise 'Invalid size parameter' unless size.nil? || size.match(/^\d+$/)
235 size = size.to_i
235 size = size.to_i
236 size = nil unless size > 0
236 size = nil unless size > 0
237 if obj && obj.respond_to?(:attachments) && attachment = Attachment.latest_attach(obj.attachments, filename)
237 if obj && obj.respond_to?(:attachments) && attachment = Attachment.latest_attach(obj.attachments, filename)
238 title = options[:title] || attachment.title
238 title = options[:title] || attachment.title
239 img = image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment, :size => size), :alt => attachment.filename)
239 img = image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment, :size => size), :alt => attachment.filename)
240 link_to(img, url_for(:controller => 'attachments', :action => 'show', :id => attachment), :class => 'thumbnail', :title => title)
240 link_to(img, url_for(:controller => 'attachments', :action => 'show', :id => attachment), :class => 'thumbnail', :title => title)
241 else
241 else
242 raise "Attachment #{filename} not found"
242 raise "Attachment #{filename} not found"
243 end
243 end
244 end
244 end
245 end
245 end
246 end
246 end
247 end
247 end
@@ -1,374 +1,390
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../../../test_helper', __FILE__)
18 require File.expand_path('../../../../../test_helper', __FILE__)
19
19
20 class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase
20 class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
23 include ActionView::Helpers::SanitizeHelper
23 include ActionView::Helpers::SanitizeHelper
24 include ERB::Util
24 include ERB::Util
25 extend ActionView::Helpers::SanitizeHelper::ClassMethods
25 extend ActionView::Helpers::SanitizeHelper::ClassMethods
26
26
27 fixtures :projects, :roles, :enabled_modules, :users,
27 fixtures :projects, :roles, :enabled_modules, :users,
28 :repositories, :changesets,
28 :repositories, :changesets,
29 :trackers, :issue_statuses, :issues,
29 :trackers, :issue_statuses, :issues,
30 :versions, :documents,
30 :versions, :documents,
31 :wikis, :wiki_pages, :wiki_contents,
31 :wikis, :wiki_pages, :wiki_contents,
32 :boards, :messages,
32 :boards, :messages,
33 :attachments
33 :attachments
34
34
35 def setup
35 def setup
36 super
36 super
37 @project = nil
37 @project = nil
38 end
38 end
39
39
40 def teardown
40 def teardown
41 end
41 end
42
42
43 def test_macro_registration
43 def test_macro_registration
44 Redmine::WikiFormatting::Macros.register do
44 Redmine::WikiFormatting::Macros.register do
45 macro :foo do |obj, args|
45 macro :foo do |obj, args|
46 "Foo: #{args.size} (#{args.join(',')}) (#{args.class.name})"
46 "Foo: #{args.size} (#{args.join(',')}) (#{args.class.name})"
47 end
47 end
48 end
48 end
49
49
50 assert_equal '<p>Foo: 0 () (Array)</p>', textilizable("{{foo}}")
50 assert_equal '<p>Foo: 0 () (Array)</p>', textilizable("{{foo}}")
51 assert_equal '<p>Foo: 0 () (Array)</p>', textilizable("{{foo()}}")
51 assert_equal '<p>Foo: 0 () (Array)</p>', textilizable("{{foo()}}")
52 assert_equal '<p>Foo: 1 (arg1) (Array)</p>', textilizable("{{foo(arg1)}}")
52 assert_equal '<p>Foo: 1 (arg1) (Array)</p>', textilizable("{{foo(arg1)}}")
53 assert_equal '<p>Foo: 2 (arg1,arg2) (Array)</p>', textilizable("{{foo(arg1, arg2)}}")
53 assert_equal '<p>Foo: 2 (arg1,arg2) (Array)</p>', textilizable("{{foo(arg1, arg2)}}")
54 end
54 end
55
55
56 def test_macro_registration_parse_args_set_to_false_should_disable_arguments_parsing
56 def test_macro_registration_parse_args_set_to_false_should_disable_arguments_parsing
57 Redmine::WikiFormatting::Macros.register do
57 Redmine::WikiFormatting::Macros.register do
58 macro :bar, :parse_args => false do |obj, args|
58 macro :bar, :parse_args => false do |obj, args|
59 "Bar: (#{args}) (#{args.class.name})"
59 "Bar: (#{args}) (#{args.class.name})"
60 end
60 end
61 end
61 end
62
62
63 assert_equal '<p>Bar: (args, more args) (String)</p>', textilizable("{{bar(args, more args)}}")
63 assert_equal '<p>Bar: (args, more args) (String)</p>', textilizable("{{bar(args, more args)}}")
64 assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar}}")
64 assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar}}")
65 assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar()}}")
65 assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar()}}")
66 end
66 end
67
67
68 def test_macro_registration_with_3_args_should_receive_text_argument
68 def test_macro_registration_with_3_args_should_receive_text_argument
69 Redmine::WikiFormatting::Macros.register do
69 Redmine::WikiFormatting::Macros.register do
70 macro :baz do |obj, args, text|
70 macro :baz do |obj, args, text|
71 "Baz: (#{args.join(',')}) (#{text.class.name}) (#{text})"
71 "Baz: (#{args.join(',')}) (#{text.class.name}) (#{text})"
72 end
72 end
73 end
73 end
74
74
75 assert_equal "<p>Baz: () (NilClass) ()</p>", textilizable("{{baz}}")
75 assert_equal "<p>Baz: () (NilClass) ()</p>", textilizable("{{baz}}")
76 assert_equal "<p>Baz: () (NilClass) ()</p>", textilizable("{{baz()}}")
76 assert_equal "<p>Baz: () (NilClass) ()</p>", textilizable("{{baz()}}")
77 assert_equal "<p>Baz: () (String) (line1\nline2)</p>", textilizable("{{baz()\nline1\nline2\n}}")
77 assert_equal "<p>Baz: () (String) (line1\nline2)</p>", textilizable("{{baz()\nline1\nline2\n}}")
78 assert_equal "<p>Baz: (arg1,arg2) (String) (line1\nline2)</p>", textilizable("{{baz(arg1, arg2)\nline1\nline2\n}}")
78 assert_equal "<p>Baz: (arg1,arg2) (String) (line1\nline2)</p>", textilizable("{{baz(arg1, arg2)\nline1\nline2\n}}")
79 end
79 end
80
80
81 def test_macro_name_with_upper_case
81 def test_macro_name_with_upper_case
82 Redmine::WikiFormatting::Macros.macro(:UpperCase) {|obj, args| "Upper"}
82 Redmine::WikiFormatting::Macros.macro(:UpperCase) {|obj, args| "Upper"}
83
83
84 assert_equal "<p>Upper</p>", textilizable("{{UpperCase}}")
84 assert_equal "<p>Upper</p>", textilizable("{{UpperCase}}")
85 end
85 end
86
86
87 def test_multiple_macros_on_the_same_line
87 def test_multiple_macros_on_the_same_line
88 Redmine::WikiFormatting::Macros.macro :foo do |obj, args|
88 Redmine::WikiFormatting::Macros.macro :foo do |obj, args|
89 args.any? ? "args: #{args.join(',')}" : "no args"
89 args.any? ? "args: #{args.join(',')}" : "no args"
90 end
90 end
91
91
92 assert_equal '<p>no args no args</p>', textilizable("{{foo}} {{foo}}")
92 assert_equal '<p>no args no args</p>', textilizable("{{foo}} {{foo}}")
93 assert_equal '<p>args: a,b no args</p>', textilizable("{{foo(a,b)}} {{foo}}")
93 assert_equal '<p>args: a,b no args</p>', textilizable("{{foo(a,b)}} {{foo}}")
94 assert_equal '<p>args: a,b args: c,d</p>', textilizable("{{foo(a,b)}} {{foo(c,d)}}")
94 assert_equal '<p>args: a,b args: c,d</p>', textilizable("{{foo(a,b)}} {{foo(c,d)}}")
95 assert_equal '<p>no args args: c,d</p>', textilizable("{{foo}} {{foo(c,d)}}")
95 assert_equal '<p>no args args: c,d</p>', textilizable("{{foo}} {{foo(c,d)}}")
96 end
96 end
97
97
98 def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute
98 def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute
99 issue = Issue.find(1)
99 issue = Issue.find(1)
100 issue.description = "{{hello_world}}"
100 issue.description = "{{hello_world}}"
101 assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(issue, :description)
101 assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(issue, :description)
102 end
102 end
103
103
104 def test_macro_should_receive_the_object_as_argument_when_called_with_object_option
104 def test_macro_should_receive_the_object_as_argument_when_called_with_object_option
105 text = "{{hello_world}}"
105 text = "{{hello_world}}"
106 assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(text, :object => Issue.find(1))
106 assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(text, :object => Issue.find(1))
107 end
107 end
108
108
109 def test_extract_macro_options_should_with_args
109 def test_extract_macro_options_should_with_args
110 options = extract_macro_options(["arg1", "arg2"], :foo, :size)
110 options = extract_macro_options(["arg1", "arg2"], :foo, :size)
111 assert_equal([["arg1", "arg2"], {}], options)
111 assert_equal([["arg1", "arg2"], {}], options)
112 end
112 end
113
113
114 def test_extract_macro_options_should_with_options
114 def test_extract_macro_options_should_with_options
115 options = extract_macro_options(["foo=bar", "size=2"], :foo, :size)
115 options = extract_macro_options(["foo=bar", "size=2"], :foo, :size)
116 assert_equal([[], {:foo => "bar", :size => "2"}], options)
116 assert_equal([[], {:foo => "bar", :size => "2"}], options)
117 end
117 end
118
118
119 def test_extract_macro_options_should_with_args_and_options
119 def test_extract_macro_options_should_with_args_and_options
120 options = extract_macro_options(["arg1", "arg2", "foo=bar", "size=2"], :foo, :size)
120 options = extract_macro_options(["arg1", "arg2", "foo=bar", "size=2"], :foo, :size)
121 assert_equal([["arg1", "arg2"], {:foo => "bar", :size => "2"}], options)
121 assert_equal([["arg1", "arg2"], {:foo => "bar", :size => "2"}], options)
122 end
122 end
123
123
124 def test_extract_macro_options_should_parse_options_lazily
124 def test_extract_macro_options_should_parse_options_lazily
125 options = extract_macro_options(["params=x=1&y=2"], :params)
125 options = extract_macro_options(["params=x=1&y=2"], :params)
126 assert_equal([[], {:params => "x=1&y=2"}], options)
126 assert_equal([[], {:params => "x=1&y=2"}], options)
127 end
127 end
128
128
129 def test_macro_exception_should_be_displayed
129 def test_macro_exception_should_be_displayed
130 Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
130 Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
131 raise "My message"
131 raise "My message"
132 end
132 end
133
133
134 text = "{{exception}}"
134 text = "{{exception}}"
135 assert_include '<div class="flash error">Error executing the <strong>exception</strong> macro (My message)</div>', textilizable(text)
135 assert_include '<div class="flash error">Error executing the <strong>exception</strong> macro (My message)</div>', textilizable(text)
136 end
136 end
137
137
138 def test_macro_arguments_should_not_be_parsed_by_formatters
138 def test_macro_arguments_should_not_be_parsed_by_formatters
139 text = '{{hello_world(http://www.redmine.org, #1)}}'
139 text = '{{hello_world(http://www.redmine.org, #1)}}'
140 assert_include 'Arguments: http://www.redmine.org, #1', textilizable(text)
140 assert_include 'Arguments: http://www.redmine.org, #1', textilizable(text)
141 end
141 end
142
142
143 def test_exclamation_mark_should_not_run_macros
143 def test_exclamation_mark_should_not_run_macros
144 text = "!{{hello_world}}"
144 text = "!{{hello_world}}"
145 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
145 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
146 end
146 end
147
147
148 def test_exclamation_mark_should_escape_macros
148 def test_exclamation_mark_should_escape_macros
149 text = "!{{hello_world(<tag>)}}"
149 text = "!{{hello_world(<tag>)}}"
150 assert_equal '<p>{{hello_world(&lt;tag&gt;)}}</p>', textilizable(text)
150 assert_equal '<p>{{hello_world(&lt;tag&gt;)}}</p>', textilizable(text)
151 end
151 end
152
152
153 def test_unknown_macros_should_not_be_replaced
153 def test_unknown_macros_should_not_be_replaced
154 text = "{{unknown}}"
154 text = "{{unknown}}"
155 assert_equal '<p>{{unknown}}</p>', textilizable(text)
155 assert_equal '<p>{{unknown}}</p>', textilizable(text)
156 end
156 end
157
157
158 def test_unknown_macros_should_parsed_as_text
158 def test_unknown_macros_should_parsed_as_text
159 text = "{{unknown(*test*)}}"
159 text = "{{unknown(*test*)}}"
160 assert_equal '<p>{{unknown(<strong>test</strong>)}}</p>', textilizable(text)
160 assert_equal '<p>{{unknown(<strong>test</strong>)}}</p>', textilizable(text)
161 end
161 end
162
162
163 def test_unknown_macros_should_be_escaped
163 def test_unknown_macros_should_be_escaped
164 text = "{{unknown(<tag>)}}"
164 text = "{{unknown(<tag>)}}"
165 assert_equal '<p>{{unknown(&lt;tag&gt;)}}</p>', textilizable(text)
165 assert_equal '<p>{{unknown(&lt;tag&gt;)}}</p>', textilizable(text)
166 end
166 end
167
167
168 def test_html_safe_macro_output_should_not_be_escaped
168 def test_html_safe_macro_output_should_not_be_escaped
169 Redmine::WikiFormatting::Macros.macro :safe_macro do |obj, args|
169 Redmine::WikiFormatting::Macros.macro :safe_macro do |obj, args|
170 "<tag>".html_safe
170 "<tag>".html_safe
171 end
171 end
172 assert_equal '<p><tag></p>', textilizable("{{safe_macro}}")
172 assert_equal '<p><tag></p>', textilizable("{{safe_macro}}")
173 end
173 end
174
174
175 def test_macro_hello_world
175 def test_macro_hello_world
176 text = "{{hello_world}}"
176 text = "{{hello_world}}"
177 assert textilizable(text).match(/Hello world!/)
177 assert textilizable(text).match(/Hello world!/)
178 end
178 end
179
179
180 def test_macro_hello_world_should_escape_arguments
180 def test_macro_hello_world_should_escape_arguments
181 text = "{{hello_world(<tag>)}}"
181 text = "{{hello_world(<tag>)}}"
182 assert_include 'Arguments: &lt;tag&gt;', textilizable(text)
182 assert_include 'Arguments: &lt;tag&gt;', textilizable(text)
183 end
183 end
184
184
185 def test_macro_macro_list
185 def test_macro_macro_list
186 text = "{{macro_list}}"
186 text = "{{macro_list}}"
187 assert_match %r{<code>hello_world</code>}, textilizable(text)
187 assert_match %r{<code>hello_world</code>}, textilizable(text)
188 end
188 end
189
189
190 def test_macro_include
190 def test_macro_include
191 @project = Project.find(1)
191 @project = Project.find(1)
192 # include a page of the current project wiki
192 # include a page of the current project wiki
193 text = "{{include(Another page)}}"
193 text = "{{include(Another page)}}"
194 assert_include 'This is a link to a ticket', textilizable(text)
194 assert_include 'This is a link to a ticket', textilizable(text)
195
195
196 @project = nil
196 @project = nil
197 # include a page of a specific project wiki
197 # include a page of a specific project wiki
198 text = "{{include(ecookbook:Another page)}}"
198 text = "{{include(ecookbook:Another page)}}"
199 assert_include 'This is a link to a ticket', textilizable(text)
199 assert_include 'This is a link to a ticket', textilizable(text)
200
200
201 text = "{{include(ecookbook:)}}"
201 text = "{{include(ecookbook:)}}"
202 assert_include 'CookBook documentation', textilizable(text)
202 assert_include 'CookBook documentation', textilizable(text)
203
203
204 text = "{{include(unknowidentifier:somepage)}}"
204 text = "{{include(unknowidentifier:somepage)}}"
205 assert_include 'Page not found', textilizable(text)
205 assert_include 'Page not found', textilizable(text)
206 end
206 end
207
207
208 def test_macro_collapse
208 def test_macro_collapse
209 text = "{{collapse\n*Collapsed* block of text\n}}"
209 text = "{{collapse\n*Collapsed* block of text\n}}"
210 result = textilizable(text)
210 result = textilizable(text)
211
211
212 assert_select_in result, 'div.collapsed-text'
212 assert_select_in result, 'div.collapsed-text'
213 assert_select_in result, 'strong', :text => 'Collapsed'
213 assert_select_in result, 'strong', :text => 'Collapsed'
214 assert_select_in result, 'a.collapsible.collapsed', :text => 'Show'
214 assert_select_in result, 'a.collapsible.collapsed', :text => 'Show'
215 assert_select_in result, 'a.collapsible', :text => 'Hide'
215 assert_select_in result, 'a.collapsible', :text => 'Hide'
216 end
216 end
217
217
218 def test_macro_collapse_with_one_arg
218 def test_macro_collapse_with_one_arg
219 text = "{{collapse(Example)\n*Collapsed* block of text\n}}"
219 text = "{{collapse(Example)\n*Collapsed* block of text\n}}"
220 result = textilizable(text)
220 result = textilizable(text)
221
221
222 assert_select_in result, 'div.collapsed-text'
222 assert_select_in result, 'div.collapsed-text'
223 assert_select_in result, 'strong', :text => 'Collapsed'
223 assert_select_in result, 'strong', :text => 'Collapsed'
224 assert_select_in result, 'a.collapsible.collapsed', :text => 'Example'
224 assert_select_in result, 'a.collapsible.collapsed', :text => 'Example'
225 assert_select_in result, 'a.collapsible', :text => 'Example'
225 assert_select_in result, 'a.collapsible', :text => 'Example'
226 end
226 end
227
227
228 def test_macro_collapse_with_two_args
228 def test_macro_collapse_with_two_args
229 text = "{{collapse(Show example, Hide example)\n*Collapsed* block of text\n}}"
229 text = "{{collapse(Show example, Hide example)\n*Collapsed* block of text\n}}"
230 result = textilizable(text)
230 result = textilizable(text)
231
231
232 assert_select_in result, 'div.collapsed-text'
232 assert_select_in result, 'div.collapsed-text'
233 assert_select_in result, 'strong', :text => 'Collapsed'
233 assert_select_in result, 'strong', :text => 'Collapsed'
234 assert_select_in result, 'a.collapsible.collapsed', :text => 'Show example'
234 assert_select_in result, 'a.collapsible.collapsed', :text => 'Show example'
235 assert_select_in result, 'a.collapsible', :text => 'Hide example'
235 assert_select_in result, 'a.collapsible', :text => 'Hide example'
236 end
236 end
237
237
238 def test_macro_collapse_should_not_break_toc
239 text = <<-RAW
240 {{toc}}
241
242 h1. Title
243
244 {{collapse(Show example, Hide example)
245 h2. Heading
246 }}"
247 RAW
248
249 expected_toc = '<ul class="toc"><li><a href="#Title">Title</a><ul><li><a href="#Heading">Heading</a></li></ul></li></ul>'
250
251 assert_include expected_toc, textilizable(text).gsub(/[\r\n]/, '')
252 end
253
238 def test_macro_child_pages
254 def test_macro_child_pages
239 expected = "<p><ul class=\"pages-hierarchy\">\n" +
255 expected = "<p><ul class=\"pages-hierarchy\">\n" +
240 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a>\n" +
256 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a>\n" +
241 "<ul class=\"pages-hierarchy\">\n<li><a href=\"/projects/ecookbook/wiki/Child_1_1\">Child 1 1</a></li>\n</ul>\n</li>\n" +
257 "<ul class=\"pages-hierarchy\">\n<li><a href=\"/projects/ecookbook/wiki/Child_1_1\">Child 1 1</a></li>\n</ul>\n</li>\n" +
242 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
258 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
243 "</ul>\n</p>"
259 "</ul>\n</p>"
244
260
245 @project = Project.find(1)
261 @project = Project.find(1)
246 # child pages of the current wiki page
262 # child pages of the current wiki page
247 assert_equal expected, textilizable("{{child_pages}}", :object => WikiPage.find(2).content)
263 assert_equal expected, textilizable("{{child_pages}}", :object => WikiPage.find(2).content)
248 # child pages of another page
264 # child pages of another page
249 assert_equal expected, textilizable("{{child_pages(Another_page)}}", :object => WikiPage.find(1).content)
265 assert_equal expected, textilizable("{{child_pages(Another_page)}}", :object => WikiPage.find(1).content)
250
266
251 @project = Project.find(2)
267 @project = Project.find(2)
252 assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page)}}", :object => WikiPage.find(1).content)
268 assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page)}}", :object => WikiPage.find(1).content)
253 end
269 end
254
270
255 def test_macro_child_pages_with_parent_option
271 def test_macro_child_pages_with_parent_option
256 expected = "<p><ul class=\"pages-hierarchy\">\n" +
272 expected = "<p><ul class=\"pages-hierarchy\">\n" +
257 "<li><a href=\"/projects/ecookbook/wiki/Another_page\">Another page</a>\n" +
273 "<li><a href=\"/projects/ecookbook/wiki/Another_page\">Another page</a>\n" +
258 "<ul class=\"pages-hierarchy\">\n" +
274 "<ul class=\"pages-hierarchy\">\n" +
259 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a>\n" +
275 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a>\n" +
260 "<ul class=\"pages-hierarchy\">\n<li><a href=\"/projects/ecookbook/wiki/Child_1_1\">Child 1 1</a></li>\n</ul>\n</li>\n" +
276 "<ul class=\"pages-hierarchy\">\n<li><a href=\"/projects/ecookbook/wiki/Child_1_1\">Child 1 1</a></li>\n</ul>\n</li>\n" +
261 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
277 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
262 "</ul>\n</li>\n</ul>\n</p>"
278 "</ul>\n</li>\n</ul>\n</p>"
263
279
264 @project = Project.find(1)
280 @project = Project.find(1)
265 # child pages of the current wiki page
281 # child pages of the current wiki page
266 assert_equal expected, textilizable("{{child_pages(parent=1)}}", :object => WikiPage.find(2).content)
282 assert_equal expected, textilizable("{{child_pages(parent=1)}}", :object => WikiPage.find(2).content)
267 # child pages of another page
283 # child pages of another page
268 assert_equal expected, textilizable("{{child_pages(Another_page, parent=1)}}", :object => WikiPage.find(1).content)
284 assert_equal expected, textilizable("{{child_pages(Another_page, parent=1)}}", :object => WikiPage.find(1).content)
269
285
270 @project = Project.find(2)
286 @project = Project.find(2)
271 assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page, parent=1)}}", :object => WikiPage.find(1).content)
287 assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page, parent=1)}}", :object => WikiPage.find(1).content)
272 end
288 end
273
289
274 def test_macro_child_pages_with_depth_option
290 def test_macro_child_pages_with_depth_option
275 expected = "<p><ul class=\"pages-hierarchy\">\n" +
291 expected = "<p><ul class=\"pages-hierarchy\">\n" +
276 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" +
292 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" +
277 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
293 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
278 "</ul>\n</p>"
294 "</ul>\n</p>"
279
295
280 @project = Project.find(1)
296 @project = Project.find(1)
281 assert_equal expected, textilizable("{{child_pages(depth=1)}}", :object => WikiPage.find(2).content)
297 assert_equal expected, textilizable("{{child_pages(depth=1)}}", :object => WikiPage.find(2).content)
282 end
298 end
283
299
284 def test_macro_child_pages_without_wiki_page_should_fail
300 def test_macro_child_pages_without_wiki_page_should_fail
285 assert_match /can be called from wiki pages only/, textilizable("{{child_pages}}")
301 assert_match /can be called from wiki pages only/, textilizable("{{child_pages}}")
286 end
302 end
287
303
288 def test_macro_thumbnail
304 def test_macro_thumbnail
289 link = link_to('<img alt="testfile.PNG" src="/attachments/thumbnail/17" />'.html_safe,
305 link = link_to('<img alt="testfile.PNG" src="/attachments/thumbnail/17" />'.html_safe,
290 "/attachments/17",
306 "/attachments/17",
291 :class => "thumbnail",
307 :class => "thumbnail",
292 :title => "testfile.PNG")
308 :title => "testfile.PNG")
293 assert_equal "<p>#{link}</p>",
309 assert_equal "<p>#{link}</p>",
294 textilizable("{{thumbnail(testfile.png)}}", :object => Issue.find(14))
310 textilizable("{{thumbnail(testfile.png)}}", :object => Issue.find(14))
295 end
311 end
296
312
297 def test_macro_thumbnail_with_size
313 def test_macro_thumbnail_with_size
298 link = link_to('<img alt="testfile.PNG" src="/attachments/thumbnail/17/200" />'.html_safe,
314 link = link_to('<img alt="testfile.PNG" src="/attachments/thumbnail/17/200" />'.html_safe,
299 "/attachments/17",
315 "/attachments/17",
300 :class => "thumbnail",
316 :class => "thumbnail",
301 :title => "testfile.PNG")
317 :title => "testfile.PNG")
302 assert_equal "<p>#{link}</p>",
318 assert_equal "<p>#{link}</p>",
303 textilizable("{{thumbnail(testfile.png, size=200)}}", :object => Issue.find(14))
319 textilizable("{{thumbnail(testfile.png, size=200)}}", :object => Issue.find(14))
304 end
320 end
305
321
306 def test_macro_thumbnail_with_title
322 def test_macro_thumbnail_with_title
307 link = link_to('<img alt="testfile.PNG" src="/attachments/thumbnail/17" />'.html_safe,
323 link = link_to('<img alt="testfile.PNG" src="/attachments/thumbnail/17" />'.html_safe,
308 "/attachments/17",
324 "/attachments/17",
309 :class => "thumbnail",
325 :class => "thumbnail",
310 :title => "Cool image")
326 :title => "Cool image")
311 assert_equal "<p>#{link}</p>",
327 assert_equal "<p>#{link}</p>",
312 textilizable("{{thumbnail(testfile.png, title=Cool image)}}", :object => Issue.find(14))
328 textilizable("{{thumbnail(testfile.png, title=Cool image)}}", :object => Issue.find(14))
313 end
329 end
314
330
315 def test_macro_thumbnail_with_invalid_filename_should_fail
331 def test_macro_thumbnail_with_invalid_filename_should_fail
316 assert_include 'test.png not found',
332 assert_include 'test.png not found',
317 textilizable("{{thumbnail(test.png)}}", :object => Issue.find(14))
333 textilizable("{{thumbnail(test.png)}}", :object => Issue.find(14))
318 end
334 end
319
335
320 def test_macros_should_not_be_executed_in_pre_tags
336 def test_macros_should_not_be_executed_in_pre_tags
321 text = <<-RAW
337 text = <<-RAW
322 {{hello_world(foo)}}
338 {{hello_world(foo)}}
323
339
324 <pre>
340 <pre>
325 {{hello_world(pre)}}
341 {{hello_world(pre)}}
326 !{{hello_world(pre)}}
342 !{{hello_world(pre)}}
327 </pre>
343 </pre>
328
344
329 {{hello_world(bar)}}
345 {{hello_world(bar)}}
330 RAW
346 RAW
331
347
332 expected = <<-EXPECTED
348 expected = <<-EXPECTED
333 <p>Hello world! Object: NilClass, Arguments: foo and no block of text.</p>
349 <p>Hello world! Object: NilClass, Arguments: foo and no block of text.</p>
334
350
335 <pre>
351 <pre>
336 {{hello_world(pre)}}
352 {{hello_world(pre)}}
337 !{{hello_world(pre)}}
353 !{{hello_world(pre)}}
338 </pre>
354 </pre>
339
355
340 <p>Hello world! Object: NilClass, Arguments: bar and no block of text.</p>
356 <p>Hello world! Object: NilClass, Arguments: bar and no block of text.</p>
341 EXPECTED
357 EXPECTED
342
358
343 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
359 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
344 end
360 end
345
361
346 def test_macros_should_be_escaped_in_pre_tags
362 def test_macros_should_be_escaped_in_pre_tags
347 text = "<pre>{{hello_world(<tag>)}}</pre>"
363 text = "<pre>{{hello_world(<tag>)}}</pre>"
348 assert_equal "<pre>{{hello_world(&lt;tag&gt;)}}</pre>", textilizable(text)
364 assert_equal "<pre>{{hello_world(&lt;tag&gt;)}}</pre>", textilizable(text)
349 end
365 end
350
366
351 def test_macros_should_not_mangle_next_macros_outputs
367 def test_macros_should_not_mangle_next_macros_outputs
352 text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}'
368 text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}'
353 assert_equal '<p>{{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo and no block of text.</p>', textilizable(text)
369 assert_equal '<p>{{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo and no block of text.</p>', textilizable(text)
354 end
370 end
355
371
356 def test_macros_with_text_should_not_mangle_following_macros
372 def test_macros_with_text_should_not_mangle_following_macros
357 text = <<-RAW
373 text = <<-RAW
358 {{hello_world
374 {{hello_world
359 Line of text
375 Line of text
360 }}
376 }}
361
377
362 {{hello_world
378 {{hello_world
363 Another line of text
379 Another line of text
364 }}
380 }}
365 RAW
381 RAW
366
382
367 expected = <<-EXPECTED
383 expected = <<-EXPECTED
368 <p>Hello world! Object: NilClass, Called with no argument and a 12 bytes long block of text.</p>
384 <p>Hello world! Object: NilClass, Called with no argument and a 12 bytes long block of text.</p>
369 <p>Hello world! Object: NilClass, Called with no argument and a 20 bytes long block of text.</p>
385 <p>Hello world! Object: NilClass, Called with no argument and a 20 bytes long block of text.</p>
370 EXPECTED
386 EXPECTED
371
387
372 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
388 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
373 end
389 end
374 end
390 end
General Comments 0
You need to be logged in to leave comments. Login now