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