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