##// END OF EJS Templates
Fixed that #extract_macro_options should not be greedy (#12451)....
Jean-Philippe Lang -
r10658:14e56006a1f8
parent child
Show More
@@ -1,247 +1,247
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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_sym if name.is_a?(String)
150 name = name.to_sym if name.is_a?(String)
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}".downcase, &block
153 Definitions.send :define_method, "macro_#{name}".downcase, &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), :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,337 +1,356
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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_multiple_macros_on_the_same_line
81 def test_multiple_macros_on_the_same_line
82 Redmine::WikiFormatting::Macros.macro :foo do |obj, args|
82 Redmine::WikiFormatting::Macros.macro :foo do |obj, args|
83 args.any? ? "args: #{args.join(',')}" : "no args"
83 args.any? ? "args: #{args.join(',')}" : "no args"
84 end
84 end
85
85
86 assert_equal '<p>no args no args</p>', textilizable("{{foo}} {{foo}}")
86 assert_equal '<p>no args no args</p>', textilizable("{{foo}} {{foo}}")
87 assert_equal '<p>args: a,b no args</p>', textilizable("{{foo(a,b)}} {{foo}}")
87 assert_equal '<p>args: a,b no args</p>', textilizable("{{foo(a,b)}} {{foo}}")
88 assert_equal '<p>args: a,b args: c,d</p>', textilizable("{{foo(a,b)}} {{foo(c,d)}}")
88 assert_equal '<p>args: a,b args: c,d</p>', textilizable("{{foo(a,b)}} {{foo(c,d)}}")
89 assert_equal '<p>no args args: c,d</p>', textilizable("{{foo}} {{foo(c,d)}}")
89 assert_equal '<p>no args args: c,d</p>', textilizable("{{foo}} {{foo(c,d)}}")
90 end
90 end
91
91
92 def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute
92 def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute
93 issue = Issue.find(1)
93 issue = Issue.find(1)
94 issue.description = "{{hello_world}}"
94 issue.description = "{{hello_world}}"
95 assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(issue, :description)
95 assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(issue, :description)
96 end
96 end
97
97
98 def test_macro_should_receive_the_object_as_argument_when_called_with_object_option
98 def test_macro_should_receive_the_object_as_argument_when_called_with_object_option
99 text = "{{hello_world}}"
99 text = "{{hello_world}}"
100 assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(text, :object => Issue.find(1))
100 assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(text, :object => Issue.find(1))
101 end
101 end
102
102
103 def test_extract_macro_options_should_with_args
104 options = extract_macro_options(["arg1", "arg2"], :foo, :size)
105 assert_equal([["arg1", "arg2"], {}], options)
106 end
107
108 def test_extract_macro_options_should_with_options
109 options = extract_macro_options(["foo=bar", "size=2"], :foo, :size)
110 assert_equal([[], {:foo => "bar", :size => "2"}], options)
111 end
112
113 def test_extract_macro_options_should_with_args_and_options
114 options = extract_macro_options(["arg1", "arg2", "foo=bar", "size=2"], :foo, :size)
115 assert_equal([["arg1", "arg2"], {:foo => "bar", :size => "2"}], options)
116 end
117
118 def test_extract_macro_options_should_parse_options_lazily
119 options = extract_macro_options(["params=x=1&y=2"], :params)
120 assert_equal([[], {:params => "x=1&y=2"}], options)
121 end
103
122
104 def test_macro_exception_should_be_displayed
123 def test_macro_exception_should_be_displayed
105 Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
124 Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
106 raise "My message"
125 raise "My message"
107 end
126 end
108
127
109 text = "{{exception}}"
128 text = "{{exception}}"
110 assert_include '<div class="flash error">Error executing the <strong>exception</strong> macro (My message)</div>', textilizable(text)
129 assert_include '<div class="flash error">Error executing the <strong>exception</strong> macro (My message)</div>', textilizable(text)
111 end
130 end
112
131
113 def test_macro_arguments_should_not_be_parsed_by_formatters
132 def test_macro_arguments_should_not_be_parsed_by_formatters
114 text = '{{hello_world(http://www.redmine.org, #1)}}'
133 text = '{{hello_world(http://www.redmine.org, #1)}}'
115 assert_include 'Arguments: http://www.redmine.org, #1', textilizable(text)
134 assert_include 'Arguments: http://www.redmine.org, #1', textilizable(text)
116 end
135 end
117
136
118 def test_exclamation_mark_should_not_run_macros
137 def test_exclamation_mark_should_not_run_macros
119 text = "!{{hello_world}}"
138 text = "!{{hello_world}}"
120 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
139 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
121 end
140 end
122
141
123 def test_exclamation_mark_should_escape_macros
142 def test_exclamation_mark_should_escape_macros
124 text = "!{{hello_world(<tag>)}}"
143 text = "!{{hello_world(<tag>)}}"
125 assert_equal '<p>{{hello_world(&lt;tag&gt;)}}</p>', textilizable(text)
144 assert_equal '<p>{{hello_world(&lt;tag&gt;)}}</p>', textilizable(text)
126 end
145 end
127
146
128 def test_unknown_macros_should_not_be_replaced
147 def test_unknown_macros_should_not_be_replaced
129 text = "{{unknown}}"
148 text = "{{unknown}}"
130 assert_equal '<p>{{unknown}}</p>', textilizable(text)
149 assert_equal '<p>{{unknown}}</p>', textilizable(text)
131 end
150 end
132
151
133 def test_unknown_macros_should_parsed_as_text
152 def test_unknown_macros_should_parsed_as_text
134 text = "{{unknown(*test*)}}"
153 text = "{{unknown(*test*)}}"
135 assert_equal '<p>{{unknown(<strong>test</strong>)}}</p>', textilizable(text)
154 assert_equal '<p>{{unknown(<strong>test</strong>)}}</p>', textilizable(text)
136 end
155 end
137
156
138 def test_unknown_macros_should_be_escaped
157 def test_unknown_macros_should_be_escaped
139 text = "{{unknown(<tag>)}}"
158 text = "{{unknown(<tag>)}}"
140 assert_equal '<p>{{unknown(&lt;tag&gt;)}}</p>', textilizable(text)
159 assert_equal '<p>{{unknown(&lt;tag&gt;)}}</p>', textilizable(text)
141 end
160 end
142
161
143 def test_html_safe_macro_output_should_not_be_escaped
162 def test_html_safe_macro_output_should_not_be_escaped
144 Redmine::WikiFormatting::Macros.macro :safe_macro do |obj, args|
163 Redmine::WikiFormatting::Macros.macro :safe_macro do |obj, args|
145 "<tag>".html_safe
164 "<tag>".html_safe
146 end
165 end
147 assert_equal '<p><tag></p>', textilizable("{{safe_macro}}")
166 assert_equal '<p><tag></p>', textilizable("{{safe_macro}}")
148 end
167 end
149
168
150 def test_macro_hello_world
169 def test_macro_hello_world
151 text = "{{hello_world}}"
170 text = "{{hello_world}}"
152 assert textilizable(text).match(/Hello world!/)
171 assert textilizable(text).match(/Hello world!/)
153 end
172 end
154
173
155 def test_macro_hello_world_should_escape_arguments
174 def test_macro_hello_world_should_escape_arguments
156 text = "{{hello_world(<tag>)}}"
175 text = "{{hello_world(<tag>)}}"
157 assert_include 'Arguments: &lt;tag&gt;', textilizable(text)
176 assert_include 'Arguments: &lt;tag&gt;', textilizable(text)
158 end
177 end
159
178
160 def test_macro_macro_list
179 def test_macro_macro_list
161 text = "{{macro_list}}"
180 text = "{{macro_list}}"
162 assert_match %r{<code>hello_world</code>}, textilizable(text)
181 assert_match %r{<code>hello_world</code>}, textilizable(text)
163 end
182 end
164
183
165 def test_macro_include
184 def test_macro_include
166 @project = Project.find(1)
185 @project = Project.find(1)
167 # include a page of the current project wiki
186 # include a page of the current project wiki
168 text = "{{include(Another page)}}"
187 text = "{{include(Another page)}}"
169 assert_include 'This is a link to a ticket', textilizable(text)
188 assert_include 'This is a link to a ticket', textilizable(text)
170
189
171 @project = nil
190 @project = nil
172 # include a page of a specific project wiki
191 # include a page of a specific project wiki
173 text = "{{include(ecookbook:Another page)}}"
192 text = "{{include(ecookbook:Another page)}}"
174 assert_include 'This is a link to a ticket', textilizable(text)
193 assert_include 'This is a link to a ticket', textilizable(text)
175
194
176 text = "{{include(ecookbook:)}}"
195 text = "{{include(ecookbook:)}}"
177 assert_include 'CookBook documentation', textilizable(text)
196 assert_include 'CookBook documentation', textilizable(text)
178
197
179 text = "{{include(unknowidentifier:somepage)}}"
198 text = "{{include(unknowidentifier:somepage)}}"
180 assert_include 'Page not found', textilizable(text)
199 assert_include 'Page not found', textilizable(text)
181 end
200 end
182
201
183 def test_macro_collapse
202 def test_macro_collapse
184 text = "{{collapse\n*Collapsed* block of text\n}}"
203 text = "{{collapse\n*Collapsed* block of text\n}}"
185 result = textilizable(text)
204 result = textilizable(text)
186
205
187 assert_select_in result, 'div.collapsed-text'
206 assert_select_in result, 'div.collapsed-text'
188 assert_select_in result, 'strong', :text => 'Collapsed'
207 assert_select_in result, 'strong', :text => 'Collapsed'
189 assert_select_in result, 'a.collapsible.collapsed', :text => 'Show'
208 assert_select_in result, 'a.collapsible.collapsed', :text => 'Show'
190 assert_select_in result, 'a.collapsible', :text => 'Hide'
209 assert_select_in result, 'a.collapsible', :text => 'Hide'
191 end
210 end
192
211
193 def test_macro_collapse_with_one_arg
212 def test_macro_collapse_with_one_arg
194 text = "{{collapse(Example)\n*Collapsed* block of text\n}}"
213 text = "{{collapse(Example)\n*Collapsed* block of text\n}}"
195 result = textilizable(text)
214 result = textilizable(text)
196
215
197 assert_select_in result, 'div.collapsed-text'
216 assert_select_in result, 'div.collapsed-text'
198 assert_select_in result, 'strong', :text => 'Collapsed'
217 assert_select_in result, 'strong', :text => 'Collapsed'
199 assert_select_in result, 'a.collapsible.collapsed', :text => 'Example'
218 assert_select_in result, 'a.collapsible.collapsed', :text => 'Example'
200 assert_select_in result, 'a.collapsible', :text => 'Example'
219 assert_select_in result, 'a.collapsible', :text => 'Example'
201 end
220 end
202
221
203 def test_macro_collapse_with_two_args
222 def test_macro_collapse_with_two_args
204 text = "{{collapse(Show example, Hide example)\n*Collapsed* block of text\n}}"
223 text = "{{collapse(Show example, Hide example)\n*Collapsed* block of text\n}}"
205 result = textilizable(text)
224 result = textilizable(text)
206
225
207 assert_select_in result, 'div.collapsed-text'
226 assert_select_in result, 'div.collapsed-text'
208 assert_select_in result, 'strong', :text => 'Collapsed'
227 assert_select_in result, 'strong', :text => 'Collapsed'
209 assert_select_in result, 'a.collapsible.collapsed', :text => 'Show example'
228 assert_select_in result, 'a.collapsible.collapsed', :text => 'Show example'
210 assert_select_in result, 'a.collapsible', :text => 'Hide example'
229 assert_select_in result, 'a.collapsible', :text => 'Hide example'
211 end
230 end
212
231
213 def test_macro_child_pages
232 def test_macro_child_pages
214 expected = "<p><ul class=\"pages-hierarchy\">\n" +
233 expected = "<p><ul class=\"pages-hierarchy\">\n" +
215 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a>\n" +
234 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a>\n" +
216 "<ul class=\"pages-hierarchy\">\n<li><a href=\"/projects/ecookbook/wiki/Child_1_1\">Child 1 1</a></li>\n</ul>\n</li>\n" +
235 "<ul class=\"pages-hierarchy\">\n<li><a href=\"/projects/ecookbook/wiki/Child_1_1\">Child 1 1</a></li>\n</ul>\n</li>\n" +
217 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
236 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
218 "</ul>\n</p>"
237 "</ul>\n</p>"
219
238
220 @project = Project.find(1)
239 @project = Project.find(1)
221 # child pages of the current wiki page
240 # child pages of the current wiki page
222 assert_equal expected, textilizable("{{child_pages}}", :object => WikiPage.find(2).content)
241 assert_equal expected, textilizable("{{child_pages}}", :object => WikiPage.find(2).content)
223 # child pages of another page
242 # child pages of another page
224 assert_equal expected, textilizable("{{child_pages(Another_page)}}", :object => WikiPage.find(1).content)
243 assert_equal expected, textilizable("{{child_pages(Another_page)}}", :object => WikiPage.find(1).content)
225
244
226 @project = Project.find(2)
245 @project = Project.find(2)
227 assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page)}}", :object => WikiPage.find(1).content)
246 assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page)}}", :object => WikiPage.find(1).content)
228 end
247 end
229
248
230 def test_macro_child_pages_with_parent_option
249 def test_macro_child_pages_with_parent_option
231 expected = "<p><ul class=\"pages-hierarchy\">\n" +
250 expected = "<p><ul class=\"pages-hierarchy\">\n" +
232 "<li><a href=\"/projects/ecookbook/wiki/Another_page\">Another page</a>\n" +
251 "<li><a href=\"/projects/ecookbook/wiki/Another_page\">Another page</a>\n" +
233 "<ul class=\"pages-hierarchy\">\n" +
252 "<ul class=\"pages-hierarchy\">\n" +
234 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a>\n" +
253 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a>\n" +
235 "<ul class=\"pages-hierarchy\">\n<li><a href=\"/projects/ecookbook/wiki/Child_1_1\">Child 1 1</a></li>\n</ul>\n</li>\n" +
254 "<ul class=\"pages-hierarchy\">\n<li><a href=\"/projects/ecookbook/wiki/Child_1_1\">Child 1 1</a></li>\n</ul>\n</li>\n" +
236 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
255 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
237 "</ul>\n</li>\n</ul>\n</p>"
256 "</ul>\n</li>\n</ul>\n</p>"
238
257
239 @project = Project.find(1)
258 @project = Project.find(1)
240 # child pages of the current wiki page
259 # child pages of the current wiki page
241 assert_equal expected, textilizable("{{child_pages(parent=1)}}", :object => WikiPage.find(2).content)
260 assert_equal expected, textilizable("{{child_pages(parent=1)}}", :object => WikiPage.find(2).content)
242 # child pages of another page
261 # child pages of another page
243 assert_equal expected, textilizable("{{child_pages(Another_page, parent=1)}}", :object => WikiPage.find(1).content)
262 assert_equal expected, textilizable("{{child_pages(Another_page, parent=1)}}", :object => WikiPage.find(1).content)
244
263
245 @project = Project.find(2)
264 @project = Project.find(2)
246 assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page, parent=1)}}", :object => WikiPage.find(1).content)
265 assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page, parent=1)}}", :object => WikiPage.find(1).content)
247 end
266 end
248
267
249 def test_macro_child_pages_with_depth_option
268 def test_macro_child_pages_with_depth_option
250 expected = "<p><ul class=\"pages-hierarchy\">\n" +
269 expected = "<p><ul class=\"pages-hierarchy\">\n" +
251 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" +
270 "<li><a href=\"/projects/ecookbook/wiki/Child_1\">Child 1</a></li>\n" +
252 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
271 "<li><a href=\"/projects/ecookbook/wiki/Child_2\">Child 2</a></li>\n" +
253 "</ul>\n</p>"
272 "</ul>\n</p>"
254
273
255 @project = Project.find(1)
274 @project = Project.find(1)
256 assert_equal expected, textilizable("{{child_pages(depth=1)}}", :object => WikiPage.find(2).content)
275 assert_equal expected, textilizable("{{child_pages(depth=1)}}", :object => WikiPage.find(2).content)
257 end
276 end
258
277
259 def test_macro_child_pages_without_wiki_page_should_fail
278 def test_macro_child_pages_without_wiki_page_should_fail
260 assert_match /can be called from wiki pages only/, textilizable("{{child_pages}}")
279 assert_match /can be called from wiki pages only/, textilizable("{{child_pages}}")
261 end
280 end
262
281
263 def test_macro_thumbnail
282 def test_macro_thumbnail
264 assert_equal '<p><a href="/attachments/17" class="thumbnail" title="testfile.PNG"><img alt="testfile.PNG" src="/attachments/thumbnail/17" /></a></p>',
283 assert_equal '<p><a href="/attachments/17" class="thumbnail" title="testfile.PNG"><img alt="testfile.PNG" src="/attachments/thumbnail/17" /></a></p>',
265 textilizable("{{thumbnail(testfile.png)}}", :object => Issue.find(14))
284 textilizable("{{thumbnail(testfile.png)}}", :object => Issue.find(14))
266 end
285 end
267
286
268 def test_macro_thumbnail_with_size
287 def test_macro_thumbnail_with_size
269 assert_equal '<p><a href="/attachments/17" class="thumbnail" title="testfile.PNG"><img alt="testfile.PNG" src="/attachments/thumbnail/17/200" /></a></p>',
288 assert_equal '<p><a href="/attachments/17" class="thumbnail" title="testfile.PNG"><img alt="testfile.PNG" src="/attachments/thumbnail/17/200" /></a></p>',
270 textilizable("{{thumbnail(testfile.png, size=200)}}", :object => Issue.find(14))
289 textilizable("{{thumbnail(testfile.png, size=200)}}", :object => Issue.find(14))
271 end
290 end
272
291
273 def test_macro_thumbnail_with_title
292 def test_macro_thumbnail_with_title
274 assert_equal '<p><a href="/attachments/17" class="thumbnail" title="Cool image"><img alt="testfile.PNG" src="/attachments/thumbnail/17" /></a></p>',
293 assert_equal '<p><a href="/attachments/17" class="thumbnail" title="Cool image"><img alt="testfile.PNG" src="/attachments/thumbnail/17" /></a></p>',
275 textilizable("{{thumbnail(testfile.png, title=Cool image)}}", :object => Issue.find(14))
294 textilizable("{{thumbnail(testfile.png, title=Cool image)}}", :object => Issue.find(14))
276 end
295 end
277
296
278 def test_macro_thumbnail_with_invalid_filename_should_fail
297 def test_macro_thumbnail_with_invalid_filename_should_fail
279 assert_include 'test.png not found',
298 assert_include 'test.png not found',
280 textilizable("{{thumbnail(test.png)}}", :object => Issue.find(14))
299 textilizable("{{thumbnail(test.png)}}", :object => Issue.find(14))
281 end
300 end
282
301
283 def test_macros_should_not_be_executed_in_pre_tags
302 def test_macros_should_not_be_executed_in_pre_tags
284 text = <<-RAW
303 text = <<-RAW
285 {{hello_world(foo)}}
304 {{hello_world(foo)}}
286
305
287 <pre>
306 <pre>
288 {{hello_world(pre)}}
307 {{hello_world(pre)}}
289 !{{hello_world(pre)}}
308 !{{hello_world(pre)}}
290 </pre>
309 </pre>
291
310
292 {{hello_world(bar)}}
311 {{hello_world(bar)}}
293 RAW
312 RAW
294
313
295 expected = <<-EXPECTED
314 expected = <<-EXPECTED
296 <p>Hello world! Object: NilClass, Arguments: foo and no block of text.</p>
315 <p>Hello world! Object: NilClass, Arguments: foo and no block of text.</p>
297
316
298 <pre>
317 <pre>
299 {{hello_world(pre)}}
318 {{hello_world(pre)}}
300 !{{hello_world(pre)}}
319 !{{hello_world(pre)}}
301 </pre>
320 </pre>
302
321
303 <p>Hello world! Object: NilClass, Arguments: bar and no block of text.</p>
322 <p>Hello world! Object: NilClass, Arguments: bar and no block of text.</p>
304 EXPECTED
323 EXPECTED
305
324
306 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
325 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
307 end
326 end
308
327
309 def test_macros_should_be_escaped_in_pre_tags
328 def test_macros_should_be_escaped_in_pre_tags
310 text = "<pre>{{hello_world(<tag>)}}</pre>"
329 text = "<pre>{{hello_world(<tag>)}}</pre>"
311 assert_equal "<pre>{{hello_world(&lt;tag&gt;)}}</pre>", textilizable(text)
330 assert_equal "<pre>{{hello_world(&lt;tag&gt;)}}</pre>", textilizable(text)
312 end
331 end
313
332
314 def test_macros_should_not_mangle_next_macros_outputs
333 def test_macros_should_not_mangle_next_macros_outputs
315 text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}'
334 text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}'
316 assert_equal '<p>{{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo and no block of text.</p>', textilizable(text)
335 assert_equal '<p>{{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo and no block of text.</p>', textilizable(text)
317 end
336 end
318
337
319 def test_macros_with_text_should_not_mangle_following_macros
338 def test_macros_with_text_should_not_mangle_following_macros
320 text = <<-RAW
339 text = <<-RAW
321 {{hello_world
340 {{hello_world
322 Line of text
341 Line of text
323 }}
342 }}
324
343
325 {{hello_world
344 {{hello_world
326 Another line of text
345 Another line of text
327 }}
346 }}
328 RAW
347 RAW
329
348
330 expected = <<-EXPECTED
349 expected = <<-EXPECTED
331 <p>Hello world! Object: NilClass, Called with no argument and a 12 bytes long block of text.</p>
350 <p>Hello world! Object: NilClass, Called with no argument and a 12 bytes long block of text.</p>
332 <p>Hello world! Object: NilClass, Called with no argument and a 20 bytes long block of text.</p>
351 <p>Hello world! Object: NilClass, Called with no argument and a 20 bytes long block of text.</p>
333 EXPECTED
352 EXPECTED
334
353
335 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
354 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
336 end
355 end
337 end
356 end
General Comments 0
You need to be logged in to leave comments. Login now