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