##// END OF EJS Templates
Fixed: urls containing @ are parsed as email adress by the wiki formatter (#1456)....
Jean-Philippe Lang -
r1545:d991e46f122d
parent child
Show More
@@ -1,171 +1,175
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 'redcloth'
18 require 'redcloth'
19 require 'coderay'
19 require 'coderay'
20
20
21 module Redmine
21 module Redmine
22 module WikiFormatting
22 module WikiFormatting
23
23
24 private
24 private
25
25
26 class TextileFormatter < RedCloth
26 class TextileFormatter < RedCloth
27
27
28 # auto_link rule after textile rules so that it doesn't break !image_url! tags
28 # auto_link rule after textile rules so that it doesn't break !image_url! tags
29 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros]
29 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros]
30
30
31 def initialize(*args)
31 def initialize(*args)
32 super
32 super
33 self.hard_breaks=true
33 self.hard_breaks=true
34 self.no_span_caps=true
34 self.no_span_caps=true
35 end
35 end
36
36
37 def to_html(*rules, &block)
37 def to_html(*rules, &block)
38 @toc = []
38 @toc = []
39 @macros_runner = block
39 @macros_runner = block
40 super(*RULES).to_s
40 super(*RULES).to_s
41 end
41 end
42
42
43 private
43 private
44
44
45 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
45 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
46 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
46 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
47 def hard_break( text )
47 def hard_break( text )
48 text.gsub!( /(.)\n(?!\n|\Z|>| *(>? *[#*=]+(\s|$)|[{|]))/, "\\1<br />\n" ) if hard_breaks
48 text.gsub!( /(.)\n(?!\n|\Z|>| *(>? *[#*=]+(\s|$)|[{|]))/, "\\1<br />\n" ) if hard_breaks
49 end
49 end
50
50
51 # Patch to add code highlighting support to RedCloth
51 # Patch to add code highlighting support to RedCloth
52 def smooth_offtags( text )
52 def smooth_offtags( text )
53 unless @pre_list.empty?
53 unless @pre_list.empty?
54 ## replace <pre> content
54 ## replace <pre> content
55 text.gsub!(/<redpre#(\d+)>/) do
55 text.gsub!(/<redpre#(\d+)>/) do
56 content = @pre_list[$1.to_i]
56 content = @pre_list[$1.to_i]
57 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
57 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
58 content = "<code class=\"#{$1} CodeRay\">" +
58 content = "<code class=\"#{$1} CodeRay\">" +
59 CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline)
59 CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline)
60 end
60 end
61 content
61 content
62 end
62 end
63 end
63 end
64 end
64 end
65
65
66 # Patch to add 'table of content' support to RedCloth
66 # Patch to add 'table of content' support to RedCloth
67 def textile_p_withtoc(tag, atts, cite, content)
67 def textile_p_withtoc(tag, atts, cite, content)
68 if tag =~ /^h(\d)$/
68 if tag =~ /^h(\d)$/
69 @toc << [$1.to_i, content]
69 @toc << [$1.to_i, content]
70 end
70 end
71 content = "<a name=\"#{@toc.length}\" class=\"wiki-page\"></a>" + content
71 content = "<a name=\"#{@toc.length}\" class=\"wiki-page\"></a>" + content
72 textile_p(tag, atts, cite, content)
72 textile_p(tag, atts, cite, content)
73 end
73 end
74
74
75 alias :textile_h1 :textile_p_withtoc
75 alias :textile_h1 :textile_p_withtoc
76 alias :textile_h2 :textile_p_withtoc
76 alias :textile_h2 :textile_p_withtoc
77 alias :textile_h3 :textile_p_withtoc
77 alias :textile_h3 :textile_p_withtoc
78
78
79 def inline_toc(text)
79 def inline_toc(text)
80 text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
80 text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
81 div_class = 'toc'
81 div_class = 'toc'
82 div_class << ' right' if $1 == '>'
82 div_class << ' right' if $1 == '>'
83 div_class << ' left' if $1 == '<'
83 div_class << ' left' if $1 == '<'
84 out = "<div class=\"#{div_class}\">"
84 out = "<div class=\"#{div_class}\">"
85 @toc.each_with_index do |heading, index|
85 @toc.each_with_index do |heading, index|
86 # remove wiki links from the item
86 # remove wiki links from the item
87 toc_item = heading.last.gsub(/(\[\[|\]\])/, '')
87 toc_item = heading.last.gsub(/(\[\[|\]\])/, '')
88 # remove styles
88 # remove styles
89 # eg. %{color:red}Triggers% => Triggers
89 # eg. %{color:red}Triggers% => Triggers
90 toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
90 toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
91 out << "<a href=\"##{index+1}\" class=\"heading#{heading.first}\">#{toc_item}</a>"
91 out << "<a href=\"##{index+1}\" class=\"heading#{heading.first}\">#{toc_item}</a>"
92 end
92 end
93 out << '</div>'
93 out << '</div>'
94 out
94 out
95 end
95 end
96 end
96 end
97
97
98 MACROS_RE = /
98 MACROS_RE = /
99 (!)? # escaping
99 (!)? # escaping
100 (
100 (
101 \{\{ # opening tag
101 \{\{ # opening tag
102 ([\w]+) # macro name
102 ([\w]+) # macro name
103 (\(([^\}]*)\))? # optional arguments
103 (\(([^\}]*)\))? # optional arguments
104 \}\} # closing tag
104 \}\} # closing tag
105 )
105 )
106 /x unless const_defined?(:MACROS_RE)
106 /x unless const_defined?(:MACROS_RE)
107
107
108 def inline_macros(text)
108 def inline_macros(text)
109 text.gsub!(MACROS_RE) do
109 text.gsub!(MACROS_RE) do
110 esc, all, macro = $1, $2, $3.downcase
110 esc, all, macro = $1, $2, $3.downcase
111 args = ($5 || '').split(',').each(&:strip)
111 args = ($5 || '').split(',').each(&:strip)
112 if esc.nil?
112 if esc.nil?
113 begin
113 begin
114 @macros_runner.call(macro, args)
114 @macros_runner.call(macro, args)
115 rescue => e
115 rescue => e
116 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
116 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
117 end || all
117 end || all
118 else
118 else
119 all
119 all
120 end
120 end
121 end
121 end
122 end
122 end
123
123
124 AUTO_LINK_RE = %r{
124 AUTO_LINK_RE = %r{
125 ( # leading text
125 ( # leading text
126 <\w+.*?>| # leading HTML tag, or
126 <\w+.*?>| # leading HTML tag, or
127 [^=<>!:'"/]| # leading punctuation, or
127 [^=<>!:'"/]| # leading punctuation, or
128 ^ # beginning of line
128 ^ # beginning of line
129 )
129 )
130 (
130 (
131 (?:https?://)| # protocol spec, or
131 (?:https?://)| # protocol spec, or
132 (?:www\.) # www.*
132 (?:www\.) # www.*
133 )
133 )
134 (
134 (
135 (\S+?) # url
135 (\S+?) # url
136 (\/)? # slash
136 (\/)? # slash
137 )
137 )
138 ([^\w\=\/;]*?) # post
138 ([^\w\=\/;]*?) # post
139 (?=<|\s|$)
139 (?=<|\s|$)
140 }x unless const_defined?(:AUTO_LINK_RE)
140 }x unless const_defined?(:AUTO_LINK_RE)
141
141
142 # Turns all urls into clickable links (code from Rails).
142 # Turns all urls into clickable links (code from Rails).
143 def inline_auto_link(text)
143 def inline_auto_link(text)
144 text.gsub!(AUTO_LINK_RE) do
144 text.gsub!(AUTO_LINK_RE) do
145 all, leading, proto, url, post = $&, $1, $2, $3, $6
145 all, leading, proto, url, post = $&, $1, $2, $3, $6
146 if leading =~ /<a\s/i || leading =~ /![<>=]?/
146 if leading =~ /<a\s/i || leading =~ /![<>=]?/
147 # don't replace URL's that are already linked
147 # don't replace URL's that are already linked
148 # and URL's prefixed with ! !> !< != (textile images)
148 # and URL's prefixed with ! !> !< != (textile images)
149 all
149 all
150 else
150 else
151 %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post})
151 %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post})
152 end
152 end
153 end
153 end
154 end
154 end
155
155
156 # Turns all email addresses into clickable links (code from Rails).
156 # Turns all email addresses into clickable links (code from Rails).
157 def inline_auto_mailto(text)
157 def inline_auto_mailto(text)
158 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
158 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
159 text = $1
159 mail = $1
160 %{<a href="mailto:#{$1}" class="email">#{text}</a>}
160 if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
161 mail
162 else
163 %{<a href="mailto:#{mail}" class="email">#{mail}</a>}
164 end
161 end
165 end
162 end
166 end
163 end
167 end
164
168
165 public
169 public
166
170
167 def self.to_html(text, options = {}, &block)
171 def self.to_html(text, options = {}, &block)
168 TextileFormatter.new(text).to_html(&block)
172 TextileFormatter.new(text).to_html(&block)
169 end
173 end
170 end
174 end
171 end
175 end
@@ -1,330 +1,331
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < HelperTestCase
20 class ApplicationHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents, :roles, :enabled_modules
23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents, :roles, :enabled_modules
24
24
25 def setup
25 def setup
26 super
26 super
27 end
27 end
28
28
29 def test_auto_links
29 def test_auto_links
30 to_test = {
30 to_test = {
31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
32 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
32 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
34 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
34 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
36 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
36 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
38 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
38 }
39 }
39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
40 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
40 end
41 end
41
42
42 def test_auto_mailto
43 def test_auto_mailto
43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
44 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
44 textilizable('test@foo.bar')
45 textilizable('test@foo.bar')
45 end
46 end
46
47
47 def test_inline_images
48 def test_inline_images
48 to_test = {
49 to_test = {
49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
50 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
51 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
51 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
52 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
52 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
53 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
53 }
54 }
54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
55 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
55 end
56 end
56
57
57 def test_textile_external_links
58 def test_textile_external_links
58 to_test = {
59 to_test = {
59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
60 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
61 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
62 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
62 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
63 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
63 # no multiline link text
64 # no multiline link text
64 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
65 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
65 }
66 }
66 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
67 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
67 end
68 end
68
69
69 def test_redmine_links
70 def test_redmine_links
70 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
71 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
71 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
72 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
72
73
73 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
74 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
74 :class => 'changeset', :title => 'My very first commit')
75 :class => 'changeset', :title => 'My very first commit')
75
76
76 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
77 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
77 :class => 'document')
78 :class => 'document')
78
79
79 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
80 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
80 :class => 'version')
81 :class => 'version')
81
82
82 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => 'some/file'}
83 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => 'some/file'}
83
84
84 to_test = {
85 to_test = {
85 # tickets
86 # tickets
86 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
87 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
87 # changesets
88 # changesets
88 'r1' => changeset_link,
89 'r1' => changeset_link,
89 # documents
90 # documents
90 'document#1' => document_link,
91 'document#1' => document_link,
91 'document:"Test document"' => document_link,
92 'document:"Test document"' => document_link,
92 # versions
93 # versions
93 'version#2' => version_link,
94 'version#2' => version_link,
94 'version:1.0' => version_link,
95 'version:1.0' => version_link,
95 'version:"1.0"' => version_link,
96 'version:"1.0"' => version_link,
96 # source
97 # source
97 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
98 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
98 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
99 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
99 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
100 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
100 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
101 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
101 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
102 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
102 # escaping
103 # escaping
103 '!#3.' => '#3.',
104 '!#3.' => '#3.',
104 '!r1' => 'r1',
105 '!r1' => 'r1',
105 '!document#1' => 'document#1',
106 '!document#1' => 'document#1',
106 '!document:"Test document"' => 'document:"Test document"',
107 '!document:"Test document"' => 'document:"Test document"',
107 '!version#2' => 'version#2',
108 '!version#2' => 'version#2',
108 '!version:1.0' => 'version:1.0',
109 '!version:1.0' => 'version:1.0',
109 '!version:"1.0"' => 'version:"1.0"',
110 '!version:"1.0"' => 'version:"1.0"',
110 '!source:/some/file' => 'source:/some/file',
111 '!source:/some/file' => 'source:/some/file',
111 # invalid expressions
112 # invalid expressions
112 'source:' => 'source:',
113 'source:' => 'source:',
113 # url hash
114 # url hash
114 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
115 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
115 }
116 }
116 @project = Project.find(1)
117 @project = Project.find(1)
117 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
118 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
118 end
119 end
119
120
120 def test_wiki_links
121 def test_wiki_links
121 to_test = {
122 to_test = {
122 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
123 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
123 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
124 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
124 # page that doesn't exist
125 # page that doesn't exist
125 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
126 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
126 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
127 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
127 # link to another project wiki
128 # link to another project wiki
128 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
129 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
129 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
130 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
130 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
131 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
131 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
132 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
132 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
133 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
133 # striked through link
134 # striked through link
134 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
135 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
135 # escaping
136 # escaping
136 '![[Another page|Page]]' => '[[Another page|Page]]',
137 '![[Another page|Page]]' => '[[Another page|Page]]',
137 }
138 }
138 @project = Project.find(1)
139 @project = Project.find(1)
139 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
140 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
140 end
141 end
141
142
142 def test_html_tags
143 def test_html_tags
143 to_test = {
144 to_test = {
144 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
145 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
145 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
146 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
146 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
147 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
147 # do not escape pre/code tags
148 # do not escape pre/code tags
148 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
149 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
149 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
150 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
150 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
151 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
151 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
152 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
152 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
153 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
153 }
154 }
154 to_test.each { |text, result| assert_equal result, textilizable(text) }
155 to_test.each { |text, result| assert_equal result, textilizable(text) }
155 end
156 end
156
157
157 def test_allowed_html_tags
158 def test_allowed_html_tags
158 to_test = {
159 to_test = {
159 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
160 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
160 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
161 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
161 }
162 }
162 to_test.each { |text, result| assert_equal result, textilizable(text) }
163 to_test.each { |text, result| assert_equal result, textilizable(text) }
163 end
164 end
164
165
165 def test_wiki_links_in_tables
166 def test_wiki_links_in_tables
166 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
167 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
167 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
168 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
168 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
169 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
169 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
170 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
170 }
171 }
171 @project = Project.find(1)
172 @project = Project.find(1)
172 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
173 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
173 end
174 end
174
175
175 def test_text_formatting
176 def test_text_formatting
176 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
177 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
177 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
178 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
178 }
179 }
179 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
180 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
180 end
181 end
181
182
182 def test_wiki_horizontal_rule
183 def test_wiki_horizontal_rule
183 assert_equal '<hr />', textilizable('---')
184 assert_equal '<hr />', textilizable('---')
184 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
185 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
185 end
186 end
186
187
187 def test_table_of_content
188 def test_table_of_content
188 raw = <<-RAW
189 raw = <<-RAW
189 {{toc}}
190 {{toc}}
190
191
191 h1. Title
192 h1. Title
192
193
193 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
194 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
194
195
195 h2. Subtitle
196 h2. Subtitle
196
197
197 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
198 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
198
199
199 h2. Subtitle with %{color:red}red text%
200 h2. Subtitle with %{color:red}red text%
200
201
201 h1. Another title
202 h1. Another title
202
203
203 RAW
204 RAW
204
205
205 expected = '<div class="toc">' +
206 expected = '<div class="toc">' +
206 '<a href="#1" class="heading1">Title</a>' +
207 '<a href="#1" class="heading1">Title</a>' +
207 '<a href="#2" class="heading2">Subtitle</a>' +
208 '<a href="#2" class="heading2">Subtitle</a>' +
208 '<a href="#3" class="heading2">Subtitle with red text</a>' +
209 '<a href="#3" class="heading2">Subtitle with red text</a>' +
209 '<a href="#4" class="heading1">Another title</a>' +
210 '<a href="#4" class="heading1">Another title</a>' +
210 '</div>'
211 '</div>'
211
212
212 assert textilizable(raw).include?(expected)
213 assert textilizable(raw).include?(expected)
213 end
214 end
214
215
215 def test_blockquote
216 def test_blockquote
216 # orig raw text
217 # orig raw text
217 raw = <<-RAW
218 raw = <<-RAW
218 John said:
219 John said:
219 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
220 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
220 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
221 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
221 > * Donec odio lorem,
222 > * Donec odio lorem,
222 > * sagittis ac,
223 > * sagittis ac,
223 > * malesuada in,
224 > * malesuada in,
224 > * adipiscing eu, dolor.
225 > * adipiscing eu, dolor.
225 >
226 >
226 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
227 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
227 > Proin a tellus. Nam vel neque.
228 > Proin a tellus. Nam vel neque.
228
229
229 He's right.
230 He's right.
230 RAW
231 RAW
231
232
232 # expected html
233 # expected html
233 expected = <<-EXPECTED
234 expected = <<-EXPECTED
234 <p>John said:</p>
235 <p>John said:</p>
235 <blockquote>
236 <blockquote>
236 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
237 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
237 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
238 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
238 <ul>
239 <ul>
239 <li>Donec odio lorem,</li>
240 <li>Donec odio lorem,</li>
240 <li>sagittis ac,</li>
241 <li>sagittis ac,</li>
241 <li>malesuada in,</li>
242 <li>malesuada in,</li>
242 <li>adipiscing eu, dolor.</li>
243 <li>adipiscing eu, dolor.</li>
243 </ul>
244 </ul>
244 <blockquote>
245 <blockquote>
245 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
246 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
246 </blockquote>
247 </blockquote>
247 <p>Proin a tellus. Nam vel neque.</p>
248 <p>Proin a tellus. Nam vel neque.</p>
248 </blockquote>
249 </blockquote>
249 <p>He's right.</p>
250 <p>He's right.</p>
250 EXPECTED
251 EXPECTED
251
252
252 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
253 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
253 end
254 end
254
255
255 def test_table
256 def test_table
256 raw = <<-RAW
257 raw = <<-RAW
257 This is a table with empty cells:
258 This is a table with empty cells:
258
259
259 |cell11|cell12||
260 |cell11|cell12||
260 |cell21||cell23|
261 |cell21||cell23|
261 |cell31|cell32|cell33|
262 |cell31|cell32|cell33|
262 RAW
263 RAW
263
264
264 expected = <<-EXPECTED
265 expected = <<-EXPECTED
265 <p>This is a table with empty cells:</p>
266 <p>This is a table with empty cells:</p>
266
267
267 <table>
268 <table>
268 <tr><td>cell11</td><td>cell12</td><td></td></tr>
269 <tr><td>cell11</td><td>cell12</td><td></td></tr>
269 <tr><td>cell21</td><td></td><td>cell23</td></tr>
270 <tr><td>cell21</td><td></td><td>cell23</td></tr>
270 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
271 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
271 </table>
272 </table>
272 EXPECTED
273 EXPECTED
273
274
274 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
275 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
275 end
276 end
276
277
277 def test_macro_hello_world
278 def test_macro_hello_world
278 text = "{{hello_world}}"
279 text = "{{hello_world}}"
279 assert textilizable(text).match(/Hello world!/)
280 assert textilizable(text).match(/Hello world!/)
280 # escaping
281 # escaping
281 text = "!{{hello_world}}"
282 text = "!{{hello_world}}"
282 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
283 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
283 end
284 end
284
285
285 def test_macro_include
286 def test_macro_include
286 @project = Project.find(1)
287 @project = Project.find(1)
287 # include a page of the current project wiki
288 # include a page of the current project wiki
288 text = "{{include(Another page)}}"
289 text = "{{include(Another page)}}"
289 assert textilizable(text).match(/This is a link to a ticket/)
290 assert textilizable(text).match(/This is a link to a ticket/)
290
291
291 @project = nil
292 @project = nil
292 # include a page of a specific project wiki
293 # include a page of a specific project wiki
293 text = "{{include(ecookbook:Another page)}}"
294 text = "{{include(ecookbook:Another page)}}"
294 assert textilizable(text).match(/This is a link to a ticket/)
295 assert textilizable(text).match(/This is a link to a ticket/)
295
296
296 text = "{{include(ecookbook:)}}"
297 text = "{{include(ecookbook:)}}"
297 assert textilizable(text).match(/CookBook documentation/)
298 assert textilizable(text).match(/CookBook documentation/)
298
299
299 text = "{{include(unknowidentifier:somepage)}}"
300 text = "{{include(unknowidentifier:somepage)}}"
300 assert textilizable(text).match(/Unknow project/)
301 assert textilizable(text).match(/Unknow project/)
301 end
302 end
302
303
303 def test_date_format_default
304 def test_date_format_default
304 today = Date.today
305 today = Date.today
305 Setting.date_format = ''
306 Setting.date_format = ''
306 assert_equal l_date(today), format_date(today)
307 assert_equal l_date(today), format_date(today)
307 end
308 end
308
309
309 def test_date_format
310 def test_date_format
310 today = Date.today
311 today = Date.today
311 Setting.date_format = '%d %m %Y'
312 Setting.date_format = '%d %m %Y'
312 assert_equal today.strftime('%d %m %Y'), format_date(today)
313 assert_equal today.strftime('%d %m %Y'), format_date(today)
313 end
314 end
314
315
315 def test_time_format_default
316 def test_time_format_default
316 now = Time.now
317 now = Time.now
317 Setting.date_format = ''
318 Setting.date_format = ''
318 Setting.time_format = ''
319 Setting.time_format = ''
319 assert_equal l_datetime(now), format_time(now)
320 assert_equal l_datetime(now), format_time(now)
320 assert_equal l_time(now), format_time(now, false)
321 assert_equal l_time(now), format_time(now, false)
321 end
322 end
322
323
323 def test_time_format
324 def test_time_format
324 now = Time.now
325 now = Time.now
325 Setting.date_format = '%d %m %Y'
326 Setting.date_format = '%d %m %Y'
326 Setting.time_format = '%H %M'
327 Setting.time_format = '%H %M'
327 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
328 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
328 assert_equal now.strftime('%H %M'), format_time(now, false)
329 assert_equal now.strftime('%H %M'), format_time(now, false)
329 end
330 end
330 end
331 end
General Comments 0
You need to be logged in to leave comments. Login now