##// END OF EJS Templates
Sanitize image links and handle nils in the toc formatter. #5445...
Eric Davis -
r3697:6eea3300f88a
parent child
Show More
@@ -1,161 +1,163
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 'redcloth3'
18 require 'redcloth3'
19
19
20 module Redmine
20 module Redmine
21 module WikiFormatting
21 module WikiFormatting
22 module Textile
22 module Textile
23 class Formatter < RedCloth3
23 class Formatter < RedCloth3
24 include ActionView::Helpers::TagHelper
24 include ActionView::Helpers::TagHelper
25
25
26 # auto_link rule after textile rules so that it doesn't break !image_url! tags
26 # auto_link rule after textile rules so that it doesn't break !image_url! tags
27 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc]
27 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc]
28
28
29 def initialize(*args)
29 def initialize(*args)
30 super
30 super
31 self.hard_breaks=true
31 self.hard_breaks=true
32 self.no_span_caps=true
32 self.no_span_caps=true
33 self.filter_styles=true
33 self.filter_styles=true
34 end
34 end
35
35
36 def to_html(*rules)
36 def to_html(*rules)
37 @toc = []
37 @toc = []
38 super(*RULES).to_s
38 super(*RULES).to_s
39 end
39 end
40
40
41 private
41 private
42
42
43 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
43 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
44 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
44 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
45 def hard_break( text )
45 def hard_break( text )
46 text.gsub!( /(.)\n(?!\n|\Z|>| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
46 text.gsub!( /(.)\n(?!\n|\Z|>| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
47 end
47 end
48
48
49 # Patch to add code highlighting support to RedCloth
49 # Patch to add code highlighting support to RedCloth
50 def smooth_offtags( text )
50 def smooth_offtags( text )
51 unless @pre_list.empty?
51 unless @pre_list.empty?
52 ## replace <pre> content
52 ## replace <pre> content
53 text.gsub!(/<redpre#(\d+)>/) do
53 text.gsub!(/<redpre#(\d+)>/) do
54 content = @pre_list[$1.to_i]
54 content = @pre_list[$1.to_i]
55 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
55 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
56 content = "<code class=\"#{$1} syntaxhl\">" +
56 content = "<code class=\"#{$1} syntaxhl\">" +
57 Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
57 Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
58 end
58 end
59 content
59 content
60 end
60 end
61 end
61 end
62 end
62 end
63
63
64 # Patch to add 'table of content' support to RedCloth
64 # Patch to add 'table of content' support to RedCloth
65 def textile_p_withtoc(tag, atts, cite, content)
65 def textile_p_withtoc(tag, atts, cite, content)
66 # removes wiki links from the item
66 # removes wiki links from the item
67 toc_item = content.gsub(/(\[\[([^\]\|]*)(\|([^\]]*))?\]\])/) { $4 || $2 }
67 toc_item = content.gsub(/(\[\[([^\]\|]*)(\|([^\]]*))?\]\])/) { $4 || $2 }
68 # sanitizes titles from links
68 # sanitizes titles from links
69 # see redcloth3.rb, same as "#{pre}#{text}#{post}"
69 # see redcloth3.rb, same as "#{pre}#{text}#{post}"
70 toc_item.gsub!(LINK_RE) { $2+$4+$9 }
70 toc_item.gsub!(LINK_RE) { [$2, $4, $9].join }
71 # sanitizes image links from titles
72 toc_item.gsub!(IMAGE_RE) { [$5].join }
71 # removes styles
73 # removes styles
72 # eg. %{color:red}Triggers% => Triggers
74 # eg. %{color:red}Triggers% => Triggers
73 toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
75 toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
74
76
75 # replaces non word caracters by dashes
77 # replaces non word caracters by dashes
76 anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
78 anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
77
79
78 unless anchor.blank?
80 unless anchor.blank?
79 if tag =~ /^h(\d)$/
81 if tag =~ /^h(\d)$/
80 @toc << [$1.to_i, anchor, toc_item]
82 @toc << [$1.to_i, anchor, toc_item]
81 end
83 end
82 atts << " id=\"#{anchor}\""
84 atts << " id=\"#{anchor}\""
83 content = content + "<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a>"
85 content = content + "<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a>"
84 end
86 end
85 textile_p(tag, atts, cite, content)
87 textile_p(tag, atts, cite, content)
86 end
88 end
87
89
88 alias :textile_h1 :textile_p_withtoc
90 alias :textile_h1 :textile_p_withtoc
89 alias :textile_h2 :textile_p_withtoc
91 alias :textile_h2 :textile_p_withtoc
90 alias :textile_h3 :textile_p_withtoc
92 alias :textile_h3 :textile_p_withtoc
91
93
92 def inline_toc(text)
94 def inline_toc(text)
93 text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
95 text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
94 div_class = 'toc'
96 div_class = 'toc'
95 div_class << ' right' if $1 == '>'
97 div_class << ' right' if $1 == '>'
96 div_class << ' left' if $1 == '<'
98 div_class << ' left' if $1 == '<'
97 out = "<ul class=\"#{div_class}\">"
99 out = "<ul class=\"#{div_class}\">"
98 @toc.each do |heading|
100 @toc.each do |heading|
99 level, anchor, toc_item = heading
101 level, anchor, toc_item = heading
100 out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n"
102 out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n"
101 end
103 end
102 out << '</ul>'
104 out << '</ul>'
103 out
105 out
104 end
106 end
105 end
107 end
106
108
107 AUTO_LINK_RE = %r{
109 AUTO_LINK_RE = %r{
108 ( # leading text
110 ( # leading text
109 <\w+.*?>| # leading HTML tag, or
111 <\w+.*?>| # leading HTML tag, or
110 [^=<>!:'"/]| # leading punctuation, or
112 [^=<>!:'"/]| # leading punctuation, or
111 ^ # beginning of line
113 ^ # beginning of line
112 )
114 )
113 (
115 (
114 (?:https?://)| # protocol spec, or
116 (?:https?://)| # protocol spec, or
115 (?:s?ftps?://)|
117 (?:s?ftps?://)|
116 (?:www\.) # www.*
118 (?:www\.) # www.*
117 )
119 )
118 (
120 (
119 (\S+?) # url
121 (\S+?) # url
120 (\/)? # slash
122 (\/)? # slash
121 )
123 )
122 ([^\w\=\/;\(\)]*?) # post
124 ([^\w\=\/;\(\)]*?) # post
123 (?=<|\s|$)
125 (?=<|\s|$)
124 }x unless const_defined?(:AUTO_LINK_RE)
126 }x unless const_defined?(:AUTO_LINK_RE)
125
127
126 # Turns all urls into clickable links (code from Rails).
128 # Turns all urls into clickable links (code from Rails).
127 def inline_auto_link(text)
129 def inline_auto_link(text)
128 text.gsub!(AUTO_LINK_RE) do
130 text.gsub!(AUTO_LINK_RE) do
129 all, leading, proto, url, post = $&, $1, $2, $3, $6
131 all, leading, proto, url, post = $&, $1, $2, $3, $6
130 if leading =~ /<a\s/i || leading =~ /![<>=]?/
132 if leading =~ /<a\s/i || leading =~ /![<>=]?/
131 # don't replace URL's that are already linked
133 # don't replace URL's that are already linked
132 # and URL's prefixed with ! !> !< != (textile images)
134 # and URL's prefixed with ! !> !< != (textile images)
133 all
135 all
134 else
136 else
135 # Idea below : an URL with unbalanced parethesis and
137 # Idea below : an URL with unbalanced parethesis and
136 # ending by ')' is put into external parenthesis
138 # ending by ')' is put into external parenthesis
137 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
139 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
138 url=url[0..-2] # discard closing parenth from url
140 url=url[0..-2] # discard closing parenth from url
139 post = ")"+post # add closing parenth to post
141 post = ")"+post # add closing parenth to post
140 end
142 end
141 tag = content_tag('a', proto + url, :href => "#{proto=="www."?"http://www.":proto}#{url}", :class => 'external')
143 tag = content_tag('a', proto + url, :href => "#{proto=="www."?"http://www.":proto}#{url}", :class => 'external')
142 %(#{leading}#{tag}#{post})
144 %(#{leading}#{tag}#{post})
143 end
145 end
144 end
146 end
145 end
147 end
146
148
147 # Turns all email addresses into clickable links (code from Rails).
149 # Turns all email addresses into clickable links (code from Rails).
148 def inline_auto_mailto(text)
150 def inline_auto_mailto(text)
149 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
151 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
150 mail = $1
152 mail = $1
151 if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
153 if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
152 mail
154 mail
153 else
155 else
154 content_tag('a', mail, :href => "mailto:#{mail}", :class => "email")
156 content_tag('a', mail, :href => "mailto:#{mail}", :class => "email")
155 end
157 end
156 end
158 end
157 end
159 end
158 end
160 end
159 end
161 end
160 end
162 end
161 end
163 end
@@ -1,596 +1,600
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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 include ActionView::Helpers::DateHelper
23 include ActionView::Helpers::DateHelper
24
24
25 fixtures :projects, :roles, :enabled_modules, :users,
25 fixtures :projects, :roles, :enabled_modules, :users,
26 :repositories, :changesets,
26 :repositories, :changesets,
27 :trackers, :issue_statuses, :issues, :versions, :documents,
27 :trackers, :issue_statuses, :issues, :versions, :documents,
28 :wikis, :wiki_pages, :wiki_contents,
28 :wikis, :wiki_pages, :wiki_contents,
29 :boards, :messages,
29 :boards, :messages,
30 :attachments,
30 :attachments,
31 :enumerations
31 :enumerations
32
32
33 def setup
33 def setup
34 super
34 super
35 end
35 end
36
36
37 def test_auto_links
37 def test_auto_links
38 to_test = {
38 to_test = {
39 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
39 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
40 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
40 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
41 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
41 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
42 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
42 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
43 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
43 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
44 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
44 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
45 '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>.',
45 '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>.',
46 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
46 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
47 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
47 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
48 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
48 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
49 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
49 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
50 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
50 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
51 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
51 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
52 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
52 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
53 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
53 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
54 '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>',
54 '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>',
55 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
55 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
56 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
56 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
57 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
57 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
58 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
58 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
59 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
59 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
60 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
60 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
61 # two exclamation marks
61 # two exclamation marks
62 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
62 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
63 # escaping
63 # escaping
64 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
64 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
65 }
65 }
66 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
66 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
67 end
67 end
68
68
69 def test_auto_mailto
69 def test_auto_mailto
70 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
70 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
71 textilizable('test@foo.bar')
71 textilizable('test@foo.bar')
72 end
72 end
73
73
74 def test_inline_images
74 def test_inline_images
75 to_test = {
75 to_test = {
76 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
76 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
77 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
77 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
78 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
78 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
79 # inline styles should be stripped
79 # inline styles should be stripped
80 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
80 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
81 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
81 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
82 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
82 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
83 }
83 }
84 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
84 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
85 end
85 end
86
86
87 def test_inline_images_inside_tags
87 def test_inline_images_inside_tags
88 raw = <<-RAW
88 raw = <<-RAW
89 h1. !foo.png! Heading
89 h1. !foo.png! Heading
90
90
91 Centered image:
91 Centered image:
92
92
93 p=. !bar.gif!
93 p=. !bar.gif!
94 RAW
94 RAW
95
95
96 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
96 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
97 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
97 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
98 end
98 end
99
99
100 def test_acronyms
100 def test_acronyms
101 to_test = {
101 to_test = {
102 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
102 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
103 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
103 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
104 }
104 }
105 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
105 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
106
106
107 end
107 end
108
108
109 def test_attached_images
109 def test_attached_images
110 to_test = {
110 to_test = {
111 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
111 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
112 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
112 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
113 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
113 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
114 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
114 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
115 # link image
115 # link image
116 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3" title="This is a logo" alt="This is a logo" /></a>',
116 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3" title="This is a logo" alt="This is a logo" /></a>',
117 }
117 }
118 attachments = Attachment.find(:all)
118 attachments = Attachment.find(:all)
119 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
119 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
120 end
120 end
121
121
122 def test_textile_external_links
122 def test_textile_external_links
123 to_test = {
123 to_test = {
124 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
124 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
125 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
125 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
126 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
126 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
127 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
127 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
128 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
128 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
129 # no multiline link text
129 # no multiline link text
130 "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 />and another on a second line\":test",
130 "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 />and another on a second line\":test",
131 # mailto link
131 # mailto link
132 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
132 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
133 # two exclamation marks
133 # two exclamation marks
134 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
134 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
135 # escaping
135 # escaping
136 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
136 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
137 }
137 }
138 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
138 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
139 end
139 end
140
140
141 def test_redmine_links
141 def test_redmine_links
142 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
142 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
143 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
143 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
144
144
145 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
145 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
146 :class => 'changeset', :title => 'My very first commit')
146 :class => 'changeset', :title => 'My very first commit')
147 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
147 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
148 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
148 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
149
149
150 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
150 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
151 :class => 'document')
151 :class => 'document')
152
152
153 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
153 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
154 :class => 'version')
154 :class => 'version')
155
155
156 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
156 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
157
157
158 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
158 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
159
159
160 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
160 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
161 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
161 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
162
162
163 to_test = {
163 to_test = {
164 # tickets
164 # tickets
165 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
165 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
166 # changesets
166 # changesets
167 'r1' => changeset_link,
167 'r1' => changeset_link,
168 'r1.' => "#{changeset_link}.",
168 'r1.' => "#{changeset_link}.",
169 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
169 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
170 'r1,r2' => "#{changeset_link},#{changeset_link2}",
170 'r1,r2' => "#{changeset_link},#{changeset_link2}",
171 # documents
171 # documents
172 'document#1' => document_link,
172 'document#1' => document_link,
173 'document:"Test document"' => document_link,
173 'document:"Test document"' => document_link,
174 # versions
174 # versions
175 'version#2' => version_link,
175 'version#2' => version_link,
176 'version:1.0' => version_link,
176 'version:1.0' => version_link,
177 'version:"1.0"' => version_link,
177 'version:"1.0"' => version_link,
178 # source
178 # source
179 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
179 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
180 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
180 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
181 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
181 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
182 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
182 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
183 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
183 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
184 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
184 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
185 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
185 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
186 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
186 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
187 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
187 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
188 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
188 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
189 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
189 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
190 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
190 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
191 # message
191 # message
192 'message#4' => link_to('Post 2', message_url, :class => 'message'),
192 'message#4' => link_to('Post 2', message_url, :class => 'message'),
193 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
193 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
194 # project
194 # project
195 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
195 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
196 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
196 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
197 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
197 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
198 # escaping
198 # escaping
199 '!#3.' => '#3.',
199 '!#3.' => '#3.',
200 '!r1' => 'r1',
200 '!r1' => 'r1',
201 '!document#1' => 'document#1',
201 '!document#1' => 'document#1',
202 '!document:"Test document"' => 'document:"Test document"',
202 '!document:"Test document"' => 'document:"Test document"',
203 '!version#2' => 'version#2',
203 '!version#2' => 'version#2',
204 '!version:1.0' => 'version:1.0',
204 '!version:1.0' => 'version:1.0',
205 '!version:"1.0"' => 'version:"1.0"',
205 '!version:"1.0"' => 'version:"1.0"',
206 '!source:/some/file' => 'source:/some/file',
206 '!source:/some/file' => 'source:/some/file',
207 # not found
207 # not found
208 '#0123456789' => '#0123456789',
208 '#0123456789' => '#0123456789',
209 # invalid expressions
209 # invalid expressions
210 'source:' => 'source:',
210 'source:' => 'source:',
211 # url hash
211 # url hash
212 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
212 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
213 }
213 }
214 @project = Project.find(1)
214 @project = Project.find(1)
215 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
215 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
216 end
216 end
217
217
218 def test_attachment_links
218 def test_attachment_links
219 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
219 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
220 to_test = {
220 to_test = {
221 'attachment:error281.txt' => attachment_link
221 'attachment:error281.txt' => attachment_link
222 }
222 }
223 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
223 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
224 end
224 end
225
225
226 def test_wiki_links
226 def test_wiki_links
227 to_test = {
227 to_test = {
228 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
228 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
229 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
229 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
230 # link with anchor
230 # link with anchor
231 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
231 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
232 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
232 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
233 # page that doesn't exist
233 # page that doesn't exist
234 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
234 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
235 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
235 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
236 # link to another project wiki
236 # link to another project wiki
237 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
237 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
238 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
238 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
239 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
239 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
240 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
240 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
241 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
241 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
242 # striked through link
242 # striked through link
243 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
243 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
244 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
244 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
245 # escaping
245 # escaping
246 '![[Another page|Page]]' => '[[Another page|Page]]',
246 '![[Another page|Page]]' => '[[Another page|Page]]',
247 # project does not exist
247 # project does not exist
248 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
248 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
249 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
249 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
250 }
250 }
251 @project = Project.find(1)
251 @project = Project.find(1)
252 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
252 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
253 end
253 end
254
254
255 def test_html_tags
255 def test_html_tags
256 to_test = {
256 to_test = {
257 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
257 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
258 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
258 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
259 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
259 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
260 # do not escape pre/code tags
260 # do not escape pre/code tags
261 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
261 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
262 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
262 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
263 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
263 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
264 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
264 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
265 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
265 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
266 # remove attributes except class
266 # remove attributes except class
267 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
267 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
268 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
268 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
269 }
269 }
270 to_test.each { |text, result| assert_equal result, textilizable(text) }
270 to_test.each { |text, result| assert_equal result, textilizable(text) }
271 end
271 end
272
272
273 def test_allowed_html_tags
273 def test_allowed_html_tags
274 to_test = {
274 to_test = {
275 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
275 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
276 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
276 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
277 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
277 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
278 }
278 }
279 to_test.each { |text, result| assert_equal result, textilizable(text) }
279 to_test.each { |text, result| assert_equal result, textilizable(text) }
280 end
280 end
281
281
282 def test_pre_tags
282 def test_pre_tags
283 raw = <<-RAW
283 raw = <<-RAW
284 Before
284 Before
285
285
286 <pre>
286 <pre>
287 <prepared-statement-cache-size>32</prepared-statement-cache-size>
287 <prepared-statement-cache-size>32</prepared-statement-cache-size>
288 </pre>
288 </pre>
289
289
290 After
290 After
291 RAW
291 RAW
292
292
293 expected = <<-EXPECTED
293 expected = <<-EXPECTED
294 <p>Before</p>
294 <p>Before</p>
295 <pre>
295 <pre>
296 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
296 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
297 </pre>
297 </pre>
298 <p>After</p>
298 <p>After</p>
299 EXPECTED
299 EXPECTED
300
300
301 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
301 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
302 end
302 end
303
303
304 def test_pre_content_should_not_parse_wiki_and_redmine_links
304 def test_pre_content_should_not_parse_wiki_and_redmine_links
305 raw = <<-RAW
305 raw = <<-RAW
306 [[CookBook documentation]]
306 [[CookBook documentation]]
307
307
308 #1
308 #1
309
309
310 <pre>
310 <pre>
311 [[CookBook documentation]]
311 [[CookBook documentation]]
312
312
313 #1
313 #1
314 </pre>
314 </pre>
315 RAW
315 RAW
316
316
317 expected = <<-EXPECTED
317 expected = <<-EXPECTED
318 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
318 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
319 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
319 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
320 <pre>
320 <pre>
321 [[CookBook documentation]]
321 [[CookBook documentation]]
322
322
323 #1
323 #1
324 </pre>
324 </pre>
325 EXPECTED
325 EXPECTED
326
326
327 @project = Project.find(1)
327 @project = Project.find(1)
328 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
328 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
329 end
329 end
330
330
331 def test_non_closing_pre_blocks_should_be_closed
331 def test_non_closing_pre_blocks_should_be_closed
332 raw = <<-RAW
332 raw = <<-RAW
333 <pre><code>
333 <pre><code>
334 RAW
334 RAW
335
335
336 expected = <<-EXPECTED
336 expected = <<-EXPECTED
337 <pre><code>
337 <pre><code>
338 </code></pre>
338 </code></pre>
339 EXPECTED
339 EXPECTED
340
340
341 @project = Project.find(1)
341 @project = Project.find(1)
342 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
342 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
343 end
343 end
344
344
345 def test_syntax_highlight
345 def test_syntax_highlight
346 raw = <<-RAW
346 raw = <<-RAW
347 <pre><code class="ruby">
347 <pre><code class="ruby">
348 # Some ruby code here
348 # Some ruby code here
349 </code></pre>
349 </code></pre>
350 RAW
350 RAW
351
351
352 expected = <<-EXPECTED
352 expected = <<-EXPECTED
353 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
353 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
354 </code></pre>
354 </code></pre>
355 EXPECTED
355 EXPECTED
356
356
357 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
357 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
358 end
358 end
359
359
360 def test_wiki_links_in_tables
360 def test_wiki_links_in_tables
361 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
361 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
362 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
362 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
363 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
363 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
364 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
364 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
365 }
365 }
366 @project = Project.find(1)
366 @project = Project.find(1)
367 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
367 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
368 end
368 end
369
369
370 def test_text_formatting
370 def test_text_formatting
371 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
371 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
372 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
372 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
373 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
373 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
374 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
374 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
375 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
375 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
376 }
376 }
377 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
377 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
378 end
378 end
379
379
380 def test_wiki_horizontal_rule
380 def test_wiki_horizontal_rule
381 assert_equal '<hr />', textilizable('---')
381 assert_equal '<hr />', textilizable('---')
382 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
382 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
383 end
383 end
384
384
385 def test_acronym
385 def test_acronym
386 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
386 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
387 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
387 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
388 end
388 end
389
389
390 def test_footnotes
390 def test_footnotes
391 raw = <<-RAW
391 raw = <<-RAW
392 This is some text[1].
392 This is some text[1].
393
393
394 fn1. This is the foot note
394 fn1. This is the foot note
395 RAW
395 RAW
396
396
397 expected = <<-EXPECTED
397 expected = <<-EXPECTED
398 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
398 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
399 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
399 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
400 EXPECTED
400 EXPECTED
401
401
402 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
402 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
403 end
403 end
404
404
405 def test_table_of_content
405 def test_table_of_content
406 raw = <<-RAW
406 raw = <<-RAW
407 {{toc}}
407 {{toc}}
408
408
409 h1. Title
409 h1. Title
410
410
411 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
411 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
412
412
413 h2. Subtitle with a [[Wiki]] link
413 h2. Subtitle with a [[Wiki]] link
414
414
415 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
415 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
416
416
417 h2. Subtitle with [[Wiki|another Wiki]] link
417 h2. Subtitle with [[Wiki|another Wiki]] link
418
418
419 h2. Subtitle with %{color:red}red text%
419 h2. Subtitle with %{color:red}red text%
420
420
421 h1. Another title
421 h1. Another title
422
422
423 h2. An "Internet link":http://www.redmine.org/ inside subtitle
423 h2. An "Internet link":http://www.redmine.org/ inside subtitle
424
425 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
426
424 RAW
427 RAW
425
428
426 expected = '<ul class="toc">' +
429 expected = '<ul class="toc">' +
427 '<li class="heading1"><a href="#Title">Title</a></li>' +
430 '<li class="heading1"><a href="#Title">Title</a></li>' +
428 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
431 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
429 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
432 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
430 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
433 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
431 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
434 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
432 '<li class="heading2"><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
435 '<li class="heading2"><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
436 '<li class="heading2"><a href="#Project-Name">Project Name</a></li>' +
433 '</ul>'
437 '</ul>'
434
438
435 assert textilizable(raw).gsub("\n", "").include?(expected)
439 assert textilizable(raw).gsub("\n", "").include?(expected)
436 end
440 end
437
441
438 def test_blockquote
442 def test_blockquote
439 # orig raw text
443 # orig raw text
440 raw = <<-RAW
444 raw = <<-RAW
441 John said:
445 John said:
442 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
446 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
443 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
447 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
444 > * Donec odio lorem,
448 > * Donec odio lorem,
445 > * sagittis ac,
449 > * sagittis ac,
446 > * malesuada in,
450 > * malesuada in,
447 > * adipiscing eu, dolor.
451 > * adipiscing eu, dolor.
448 >
452 >
449 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
453 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
450 > Proin a tellus. Nam vel neque.
454 > Proin a tellus. Nam vel neque.
451
455
452 He's right.
456 He's right.
453 RAW
457 RAW
454
458
455 # expected html
459 # expected html
456 expected = <<-EXPECTED
460 expected = <<-EXPECTED
457 <p>John said:</p>
461 <p>John said:</p>
458 <blockquote>
462 <blockquote>
459 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
463 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
460 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
464 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
461 <ul>
465 <ul>
462 <li>Donec odio lorem,</li>
466 <li>Donec odio lorem,</li>
463 <li>sagittis ac,</li>
467 <li>sagittis ac,</li>
464 <li>malesuada in,</li>
468 <li>malesuada in,</li>
465 <li>adipiscing eu, dolor.</li>
469 <li>adipiscing eu, dolor.</li>
466 </ul>
470 </ul>
467 <blockquote>
471 <blockquote>
468 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
472 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
469 </blockquote>
473 </blockquote>
470 <p>Proin a tellus. Nam vel neque.</p>
474 <p>Proin a tellus. Nam vel neque.</p>
471 </blockquote>
475 </blockquote>
472 <p>He's right.</p>
476 <p>He's right.</p>
473 EXPECTED
477 EXPECTED
474
478
475 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
479 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
476 end
480 end
477
481
478 def test_table
482 def test_table
479 raw = <<-RAW
483 raw = <<-RAW
480 This is a table with empty cells:
484 This is a table with empty cells:
481
485
482 |cell11|cell12||
486 |cell11|cell12||
483 |cell21||cell23|
487 |cell21||cell23|
484 |cell31|cell32|cell33|
488 |cell31|cell32|cell33|
485 RAW
489 RAW
486
490
487 expected = <<-EXPECTED
491 expected = <<-EXPECTED
488 <p>This is a table with empty cells:</p>
492 <p>This is a table with empty cells:</p>
489
493
490 <table>
494 <table>
491 <tr><td>cell11</td><td>cell12</td><td></td></tr>
495 <tr><td>cell11</td><td>cell12</td><td></td></tr>
492 <tr><td>cell21</td><td></td><td>cell23</td></tr>
496 <tr><td>cell21</td><td></td><td>cell23</td></tr>
493 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
497 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
494 </table>
498 </table>
495 EXPECTED
499 EXPECTED
496
500
497 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
501 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
498 end
502 end
499
503
500 def test_table_with_line_breaks
504 def test_table_with_line_breaks
501 raw = <<-RAW
505 raw = <<-RAW
502 This is a table with line breaks:
506 This is a table with line breaks:
503
507
504 |cell11
508 |cell11
505 continued|cell12||
509 continued|cell12||
506 |-cell21-||cell23
510 |-cell21-||cell23
507 cell23 line2
511 cell23 line2
508 cell23 *line3*|
512 cell23 *line3*|
509 |cell31|cell32
513 |cell31|cell32
510 cell32 line2|cell33|
514 cell32 line2|cell33|
511
515
512 RAW
516 RAW
513
517
514 expected = <<-EXPECTED
518 expected = <<-EXPECTED
515 <p>This is a table with line breaks:</p>
519 <p>This is a table with line breaks:</p>
516
520
517 <table>
521 <table>
518 <tr>
522 <tr>
519 <td>cell11<br />continued</td>
523 <td>cell11<br />continued</td>
520 <td>cell12</td>
524 <td>cell12</td>
521 <td></td>
525 <td></td>
522 </tr>
526 </tr>
523 <tr>
527 <tr>
524 <td><del>cell21</del></td>
528 <td><del>cell21</del></td>
525 <td></td>
529 <td></td>
526 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
530 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
527 </tr>
531 </tr>
528 <tr>
532 <tr>
529 <td>cell31</td>
533 <td>cell31</td>
530 <td>cell32<br/>cell32 line2</td>
534 <td>cell32<br/>cell32 line2</td>
531 <td>cell33</td>
535 <td>cell33</td>
532 </tr>
536 </tr>
533 </table>
537 </table>
534 EXPECTED
538 EXPECTED
535
539
536 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
540 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
537 end
541 end
538
542
539 def test_textile_should_not_mangle_brackets
543 def test_textile_should_not_mangle_brackets
540 assert_equal '<p>[msg1][msg2]</p>', textilizable('[msg1][msg2]')
544 assert_equal '<p>[msg1][msg2]</p>', textilizable('[msg1][msg2]')
541 end
545 end
542
546
543 def test_default_formatter
547 def test_default_formatter
544 Setting.text_formatting = 'unknown'
548 Setting.text_formatting = 'unknown'
545 text = 'a *link*: http://www.example.net/'
549 text = 'a *link*: http://www.example.net/'
546 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
550 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
547 Setting.text_formatting = 'textile'
551 Setting.text_formatting = 'textile'
548 end
552 end
549
553
550 def test_due_date_distance_in_words
554 def test_due_date_distance_in_words
551 to_test = { Date.today => 'Due in 0 days',
555 to_test = { Date.today => 'Due in 0 days',
552 Date.today + 1 => 'Due in 1 day',
556 Date.today + 1 => 'Due in 1 day',
553 Date.today + 100 => 'Due in about 3 months',
557 Date.today + 100 => 'Due in about 3 months',
554 Date.today + 20000 => 'Due in over 54 years',
558 Date.today + 20000 => 'Due in over 54 years',
555 Date.today - 1 => '1 day late',
559 Date.today - 1 => '1 day late',
556 Date.today - 100 => 'about 3 months late',
560 Date.today - 100 => 'about 3 months late',
557 Date.today - 20000 => 'over 54 years late',
561 Date.today - 20000 => 'over 54 years late',
558 }
562 }
559 to_test.each do |date, expected|
563 to_test.each do |date, expected|
560 assert_equal expected, due_date_distance_in_words(date)
564 assert_equal expected, due_date_distance_in_words(date)
561 end
565 end
562 end
566 end
563
567
564 def test_avatar
568 def test_avatar
565 # turn on avatars
569 # turn on avatars
566 Setting.gravatar_enabled = '1'
570 Setting.gravatar_enabled = '1'
567 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
571 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
568 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
572 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
569 assert_nil avatar('jsmith')
573 assert_nil avatar('jsmith')
570 assert_nil avatar(nil)
574 assert_nil avatar(nil)
571
575
572 # turn off avatars
576 # turn off avatars
573 Setting.gravatar_enabled = '0'
577 Setting.gravatar_enabled = '0'
574 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
578 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
575 end
579 end
576
580
577 def test_link_to_user
581 def test_link_to_user
578 user = User.find(2)
582 user = User.find(2)
579 t = link_to_user(user)
583 t = link_to_user(user)
580 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
584 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
581 end
585 end
582
586
583 def test_link_to_user_should_not_link_to_locked_user
587 def test_link_to_user_should_not_link_to_locked_user
584 user = User.find(5)
588 user = User.find(5)
585 assert user.locked?
589 assert user.locked?
586 t = link_to_user(user)
590 t = link_to_user(user)
587 assert_equal user.name, t
591 assert_equal user.name, t
588 end
592 end
589
593
590 def test_link_to_user_should_not_link_to_anonymous
594 def test_link_to_user_should_not_link_to_anonymous
591 user = User.anonymous
595 user = User.anonymous
592 assert user.anonymous?
596 assert user.anonymous?
593 t = link_to_user(user)
597 t = link_to_user(user)
594 assert_equal ::I18n.t(:label_user_anonymous), t
598 assert_equal ::I18n.t(:label_user_anonymous), t
595 end
599 end
596 end
600 end
General Comments 0
You need to be logged in to leave comments. Login now