"Fossies" - the Fresh Open Source Software Archive

Member "redmine-4.1.1/test/helpers/application_helper_test.rb" (6 Apr 2020, 86786 Bytes) of package /linux/www/redmine-4.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested text file into HTML format (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "application_helper_test.rb": 4.1.0_vs_4.1.1.

    1 # frozen_string_literal: true
    2 
    3 # Redmine - project management software
    4 # Copyright (C) 2006-2019  Jean-Philippe Lang
    5 #
    6 # This program is free software; you can redistribute it and/or
    7 # modify it under the terms of the GNU General Public License
    8 # as published by the Free Software Foundation; either version 2
    9 # of the License, or (at your option) any later version.
   10 #
   11 # This program is distributed in the hope that it will be useful,
   12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 # GNU General Public License for more details.
   15 #
   16 # You should have received a copy of the GNU General Public License
   17 # along with this program; if not, write to the Free Software
   18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   19 
   20 require File.expand_path('../../test_helper', __FILE__)
   21 
   22 class ApplicationHelperTest < Redmine::HelperTest
   23   include ERB::Util
   24   include Rails.application.routes.url_helpers
   25 
   26   fixtures :projects, :enabled_modules,
   27            :users, :email_addresses,
   28            :members, :member_roles, :roles,
   29            :repositories, :changesets,
   30            :projects_trackers,
   31            :trackers, :issue_statuses, :issues, :versions, :documents, :journals,
   32            :wikis, :wiki_pages, :wiki_contents,
   33            :boards, :messages, :news,
   34            :attachments, :enumerations,
   35            :custom_values, :custom_fields, :custom_fields_projects
   36 
   37   def setup
   38     super
   39     set_tmp_attachments_directory
   40     @russian_test = 'тест'
   41   end
   42 
   43   test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do
   44     User.current = User.find_by_login('admin')
   45 
   46     @project = Issue.first.project # Used by helper
   47     response = link_to_if_authorized(
   48                  'By controller/actionr',
   49                  {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
   50     assert_match /href/, response
   51   end
   52 
   53   test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do
   54     User.current = User.find_by_login('dlopper')
   55     @project = Project.find('private-child')
   56     issue = @project.issues.first
   57     assert !issue.visible?
   58     response = link_to_if_authorized(
   59                  'Never displayed',
   60                  {:controller => 'issues', :action => 'show', :id => issue})
   61     assert_nil response
   62   end
   63 
   64   def test_auto_links
   65     to_test = {
   66       'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
   67       'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
   68       'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
   69       'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
   70       'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
   71       'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
   72       '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>.',
   73       'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
   74       '(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>)',
   75       '(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>)',
   76       '(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>).',
   77       '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
   78       '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
   79       '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
   80       'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
   81       '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>',
   82       'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
   83       'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
   84       'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
   85       'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
   86       'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
   87       'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
   88       # two exclamation marks
   89       '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>',
   90       # escaping
   91       'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
   92       # wrap in angle brackets
   93       '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;',
   94       # invalid urls
   95       'http://' => 'http://',
   96       'www.' => 'www.',
   97       'test-www.bar.com' => 'test-www.bar.com',
   98       # ends with a hyphen
   99       'http://www.redmine.org/example-' => '<a class="external" href="http://www.redmine.org/example-">http://www.redmine.org/example-</a>',
  100     }
  101     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  102   end
  103 
  104   def test_auto_links_with_non_ascii_characters
  105     to_test = {
  106       "http://foo.bar/#{@russian_test}" =>
  107         %|<a class="external" href="http://foo.bar/#{@russian_test}">http://foo.bar/#{@russian_test}</a>|
  108     }
  109     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  110   end
  111 
  112   def test_auto_mailto
  113     to_test = {
  114       'test@foo.bar' => '<a class="email" href="mailto:test@foo.bar">test@foo.bar</a>',
  115       'test@www.foo.bar' => '<a class="email" href="mailto:test@www.foo.bar">test@www.foo.bar</a>',
  116     }
  117     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  118   end
  119 
  120   def test_inline_images
  121     to_test = {
  122       '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
  123       'floating !>http://foo.bar/image.jpg!' => 'floating <span style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></span>',
  124       'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="wiki-class-some-class" alt="" />',
  125       'with class !(wiki-class-foo)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="wiki-class-foo" alt="" />',
  126       'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
  127       '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" />',
  128       '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;" />',
  129     }
  130     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  131   end
  132 
  133   def test_inline_images_inside_tags
  134     raw = <<~RAW
  135       h1. !foo.png! Heading
  136 
  137       Centered image:
  138 
  139       p=. !bar.gif!
  140     RAW
  141     assert textilizable(raw).include?('<img src="foo.png" alt="" />')
  142     assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
  143   end
  144 
  145   def test_attached_images
  146     to_test = {
  147       'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
  148       'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
  149       'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
  150       'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
  151       # link image
  152       '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" /></a>',
  153     }
  154     attachments = Attachment.all
  155     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
  156   end
  157 
  158   def test_attached_images_with_textile_and_non_ascii_filename
  159     to_test = {
  160       'CAFÉ.JPG' => 'CAF%C3%89.JPG',
  161       'crème.jpg' => 'cr%C3%A8me.jpg',
  162     }
  163     with_settings :text_formatting => 'textile' do
  164       to_test.each do |filename, result|
  165         attachment = Attachment.generate!(:filename => filename)
  166         assert_include %(<img src="/attachments/download/#{attachment.id}/#{result}" alt="" />), textilizable("!#{filename}!", :attachments => [attachment])
  167       end
  168     end
  169   end
  170 
  171   def test_attached_images_with_markdown_and_non_ascii_filename
  172     skip unless Object.const_defined?(:Redcarpet)
  173 
  174     to_test = {
  175       'CAFÉ.JPG' => 'CAF%C3%89.JPG',
  176       'crème.jpg' => 'cr%C3%A8me.jpg',
  177     }
  178     with_settings :text_formatting => 'markdown' do
  179       to_test.each do |filename, result|
  180         attachment = Attachment.generate!(:filename => filename)
  181         assert_include %(<img src="/attachments/download/#{attachment.id}/#{result}" alt="" />), textilizable("![](#{filename})", :attachments => [attachment])
  182       end
  183     end
  184   end
  185 
  186   def test_attached_images_with_hires_naming
  187     attachment = Attachment.generate!(:filename => 'image@2x.png')
  188     assert_equal(
  189         %(<p><img src="/attachments/download/#{attachment.id}/image@2x.png" srcset="/attachments/download/#{attachment.id}/image@2x.png 2x" alt="" /></p>),
  190         textilizable("!image@2x.png!", :attachments => [attachment]))
  191   end
  192 
  193   def test_attached_images_filename_extension
  194     a1 = Attachment.new(
  195             :container => Issue.find(1),
  196             :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
  197             :author => User.find(1))
  198     assert a1.save
  199     assert_equal "testtest.JPG", a1.filename
  200     assert_equal "image/jpeg", a1.content_type
  201     assert a1.image?
  202 
  203     a2 = Attachment.new(
  204             :container => Issue.find(1),
  205             :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
  206             :author => User.find(1))
  207     assert a2.save
  208     assert_equal "testtest.jpeg", a2.filename
  209     assert_equal "image/jpeg", a2.content_type
  210     assert a2.image?
  211 
  212     a3 = Attachment.new(
  213             :container => Issue.find(1),
  214             :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
  215             :author => User.find(1))
  216     assert a3.save
  217     assert_equal "testtest.JPE", a3.filename
  218     assert_equal "image/jpeg", a3.content_type
  219     assert a3.image?
  220 
  221     a4 = Attachment.new(
  222             :container => Issue.find(1),
  223             :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
  224             :author => User.find(1))
  225     assert a4.save
  226     assert_equal "Testtest.BMP", a4.filename
  227     assert_equal "image/x-ms-bmp", a4.content_type
  228     assert a4.image?
  229 
  230     to_test = {
  231       'Inline image: !testtest.jpg!' =>
  232         'Inline image: <img src="/attachments/download/' + a1.id.to_s + '/testtest.JPG" alt="" />',
  233       'Inline image: !testtest.jpeg!' =>
  234         'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testtest.jpeg" alt="" />',
  235       'Inline image: !testtest.jpe!' =>
  236         'Inline image: <img src="/attachments/download/' + a3.id.to_s + '/testtest.JPE" alt="" />',
  237       'Inline image: !testtest.bmp!' =>
  238         'Inline image: <img src="/attachments/download/' + a4.id.to_s + '/Testtest.BMP" alt="" />',
  239     }
  240 
  241     attachments = [a1, a2, a3, a4]
  242     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
  243   end
  244 
  245   def test_attached_images_should_read_later
  246     set_fixtures_attachments_directory
  247     a1 = Attachment.find(16)
  248     assert_equal "testfile.png", a1.filename
  249     assert a1.readable?
  250     assert (! a1.visible?(User.anonymous))
  251     assert a1.visible?(User.find(2))
  252     a2 = Attachment.find(17)
  253     assert_equal "testfile.PNG", a2.filename
  254     assert a2.readable?
  255     assert (! a2.visible?(User.anonymous))
  256     assert a2.visible?(User.find(2))
  257     assert a1.created_on < a2.created_on
  258 
  259     to_test = {
  260       'Inline image: !testfile.png!' =>
  261         'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
  262       'Inline image: !Testfile.PNG!' =>
  263         'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
  264     }
  265     attachments = [a1, a2]
  266     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
  267   ensure
  268     set_tmp_attachments_directory
  269   end
  270 
  271   def test_textile_external_links
  272     to_test = {
  273       'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
  274       'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
  275       '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
  276       '"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>',
  277       "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
  278       # no multiline link text
  279       "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",
  280       # mailto link
  281       "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
  282       # two exclamation marks
  283       '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
  284       # escaping
  285       '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
  286       # ends with a hyphen
  287       '(see "inline link":http://www.foo.bar/Test-)' => '(see <a href="http://www.foo.bar/Test-" class="external">inline link</a>)',
  288       '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>',
  289       'This is an intern "link":/foo/bar-' => 'This is an intern <a href="/foo/bar-">link</a>',    }
  290     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  291   end
  292 
  293   def test_textile_external_links_with_non_ascii_characters
  294     to_test = {
  295       %|This is a "link":http://foo.bar/#{@russian_test}| =>
  296         %|This is a <a href="http://foo.bar/#{@russian_test}" class="external">link</a>|
  297     }
  298     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  299   end
  300 
  301   def test_redmine_links
  302     user_with_email_login = User.generate!(:login => 'abcd@example.com')
  303     user_with_email_login_2 = User.generate!(:login => 'foo.bar@example.com')
  304     u_email_id = user_with_email_login.id
  305     u_email_id_2 = user_with_email_login_2.id
  306 
  307     issue_link = link_to('#3',
  308                          {:controller => 'issues', :action => 'show', :id => 3},
  309                          :class => Issue.find(3).css_classes,
  310                          :title => 'Bug: Error 281 when updating a recipe (New)')
  311     ext_issue_link = link_to(
  312                          'Bug #3: Error 281 when updating a recipe',
  313                          {:controller => 'issues', :action => 'show', :id => 3},
  314                          :class => Issue.find(3).css_classes,
  315                          :title => 'Status: New')
  316     note_link = link_to(
  317                          '#3-14',
  318                          {:controller => 'issues', :action => 'show',
  319                           :id => 3, :anchor => 'note-14'},
  320                          :class => Issue.find(3).css_classes,
  321                          :title => 'Bug: Error 281 when updating a recipe (New)')
  322     ext_note_link = link_to(
  323                          'Bug #3-14: Error 281 when updating a recipe',
  324                          {:controller => 'issues', :action => 'show',
  325                           :id => 3, :anchor => 'note-14'},
  326                          :class => Issue.find(3).css_classes,
  327                          :title => 'Status: New')
  328     note_link2 = link_to(
  329                          '#3#note-14',
  330                          {:controller => 'issues', :action => 'show',
  331                           :id => 3, :anchor => 'note-14'},
  332                          :class => Issue.find(3).css_classes,
  333                          :title => 'Bug: Error 281 when updating a recipe (New)')
  334     ext_note_link2 = link_to(
  335                          'Bug #3#note-14: Error 281 when updating a recipe',
  336                          {:controller => 'issues', :action => 'show',
  337                           :id => 3, :anchor => 'note-14'},
  338                          :class => Issue.find(3).css_classes,
  339                          :title => 'Status: New')
  340 
  341     revision_link = link_to(
  342                          'r1',
  343                          {:controller => 'repositories', :action => 'revision',
  344                           :id => 'ecookbook', :repository_id => 10, :rev => 1},
  345                          :class => 'changeset',
  346                          :title => 'My very first commit do not escaping #<>&')
  347     revision_link2 = link_to(
  348                          'r2',
  349                          {:controller => 'repositories', :action => 'revision',
  350                           :id => 'ecookbook', :repository_id => 10, :rev => 2},
  351                          :class => 'changeset',
  352                          :title => 'This commit fixes #1, #2 and references #1 & #3')
  353 
  354     changeset_link2 = link_to(
  355                          '691322a8eb01e11fd7',
  356                          {:controller => 'repositories', :action => 'revision',
  357                           :id => 'ecookbook', :repository_id => 10, :rev => 1},
  358                          :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
  359 
  360     document_link = link_to(
  361                          'Test document',
  362                          {:controller => 'documents', :action => 'show', :id => 1},
  363                          :class => 'document')
  364 
  365     version_link = link_to('1.0',
  366                            {:controller => 'versions', :action => 'show', :id => 2},
  367                            :class => 'version')
  368 
  369     board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
  370 
  371     message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
  372 
  373     news_url = {:controller => 'news', :action => 'show', :id => 1}
  374 
  375     project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
  376 
  377     source_url = '/projects/ecookbook/repository/10/entry/some/file'
  378     source_url_with_rev = '/projects/ecookbook/repository/10/revisions/52/entry/some/file'
  379     source_url_with_ext = '/projects/ecookbook/repository/10/entry/some/file.ext'
  380     source_url_with_rev_and_ext = '/projects/ecookbook/repository/10/revisions/52/entry/some/file.ext'
  381     source_url_with_branch = '/projects/ecookbook/repository/10/revisions/branch/entry/some/file'
  382 
  383     export_url = '/projects/ecookbook/repository/10/raw/some/file'
  384     export_url_with_rev = '/projects/ecookbook/repository/10/revisions/52/raw/some/file'
  385     export_url_with_ext = '/projects/ecookbook/repository/10/raw/some/file.ext'
  386     export_url_with_rev_and_ext = '/projects/ecookbook/repository/10/revisions/52/raw/some/file.ext'
  387     export_url_with_branch = '/projects/ecookbook/repository/10/revisions/branch/raw/some/file'
  388 
  389     to_test = {
  390       # tickets
  391       '#3, [#3], (#3) and #3.'      => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
  392       # ticket notes
  393       '#3-14'                       => note_link,
  394       '#3#note-14'                  => note_link2,
  395       # should not ignore leading zero
  396       '#03'                         => '#03',
  397       # tickets with more info
  398       '##3, [##3], (##3) and ##3.'  => "#{ext_issue_link}, [#{ext_issue_link}], (#{ext_issue_link}) and #{ext_issue_link}.",
  399       '##3-14'                      => ext_note_link,
  400       '##3#note-14'                 => ext_note_link2,
  401       '##03'                        => '##03',
  402       # changesets
  403       'r1'                          => revision_link,
  404       'r1.'                         => "#{revision_link}.",
  405       'r1, r2'                      => "#{revision_link}, #{revision_link2}",
  406       'r1,r2'                       => "#{revision_link},#{revision_link2}",
  407       'commit:691322a8eb01e11fd7'   => changeset_link2,
  408       # documents
  409       'document#1'                  => document_link,
  410       'document:"Test document"'    => document_link,
  411       # versions
  412       'version#2'                   => version_link,
  413       'version:1.0'                 => version_link,
  414       'version:"1.0"'               => version_link,
  415       # source
  416       'source:some/file'            => link_to('source:some/file', source_url, :class => 'source'),
  417       'source:/some/file'           => link_to('source:/some/file', source_url, :class => 'source'),
  418       'source:/some/file.'          => link_to('source:/some/file', source_url, :class => 'source') + ".",
  419       'source:/some/file.ext.'      => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
  420       'source:/some/file. '         => link_to('source:/some/file', source_url, :class => 'source') + ".",
  421       'source:/some/file.ext. '     => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
  422       'source:/some/file, '         => link_to('source:/some/file', source_url, :class => 'source') + ",",
  423       'source:/some/file@52'        => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'),
  424       'source:/some/file@branch'    => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'),
  425       'source:/some/file.ext@52'    => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'),
  426       'source:/some/file#L110'      => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'),
  427       'source:/some/file.ext#L110'  => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'),
  428       'source:/some/file@52#L110'   => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'),
  429       # export
  430       'export:/some/file'           => link_to('export:/some/file', export_url, :class => 'source download'),
  431       'export:/some/file.ext'       => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'),
  432       'export:/some/file@52'        => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'),
  433       'export:/some/file.ext@52'    => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'),
  434       'export:/some/file@branch'    => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'),
  435       # forum
  436       'forum#2'                     => link_to('Discussion', board_url, :class => 'board'),
  437       'forum:Discussion'            => link_to('Discussion', board_url, :class => 'board'),
  438       # message
  439       'message#4'                   => link_to('Post 2', message_url, :class => 'message'),
  440       'message#5'                   => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
  441       # news
  442       'news#1'                      => link_to('eCookbook first release !', news_url, :class => 'news'),
  443       'news:"eCookbook first release !"'        => link_to('eCookbook first release !', news_url, :class => 'news'),
  444       # project
  445       'project#3'                   => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
  446       'project:subproject1'         => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
  447       'project:"eCookbook subProject 1"'        => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
  448       # not found
  449       '#0123456789'                 => '#0123456789',
  450       # invalid expressions
  451       'source:'                     => 'source:',
  452       # url hash
  453       "http://foo.bar/FAQ#3"        => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
  454       # user
  455       'user:jsmith'                 => link_to_user(User.find_by_id(2)),
  456       'user:JSMITH'                 => link_to_user(User.find_by_id(2)),
  457       'user#2'                      => link_to_user(User.find_by_id(2)),
  458       '@jsmith'                     => link_to_user(User.find_by_id(2)),
  459       '@JSMITH'                     => link_to_user(User.find_by_id(2)),
  460       '@abcd@example.com'           => link_to_user(User.find_by_id(u_email_id)),
  461       'user:abcd@example.com'       => link_to_user(User.find_by_id(u_email_id)),
  462       '@foo.bar@example.com'        => link_to_user(User.find_by_id(u_email_id_2)),
  463       'user:foo.bar@example.com'    => link_to_user(User.find_by_id(u_email_id_2)),
  464       # invalid user
  465       'user:foobar'                 => 'user:foobar',
  466     }
  467     @project = Project.find(1)
  468     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
  469   end
  470 
  471   def test_link_to_note_within_the_same_page
  472     issue = Issue.find(1)
  473     assert_equal '<p><a href="#note-14">#note-14</a></p>', textilizable('#note-14', :object => issue)
  474 
  475     journal = Journal.find(2)
  476     assert_equal '<p><a href="#note-2">#note-2</a></p>', textilizable('#note-2', :object => journal)
  477   end
  478 
  479   def test_user_links_with_email_as_login_name_should_not_be_parsed_textile
  480     with_settings :text_formatting => 'textile' do
  481       u = User.generate!(:login => 'jsmith@somenet.foo')
  482 
  483       # user link format: @jsmith@somenet.foo
  484       raw = "@jsmith@somenet.foo should not be parsed in jsmith@somenet.foo"
  485       assert_match(
  486         %r{<p><a class="user active".*>#{u.name}</a> should not be parsed in <a class="email" href="mailto:jsmith@somenet.foo">jsmith@somenet.foo</a></p>},
  487         textilizable(raw, :project => Project.find(1)))
  488 
  489       # user link format: user:jsmith@somenet.foo
  490       raw = "user:jsmith@somenet.foo should not be parsed in jsmith@somenet.foo"
  491       assert_match(
  492         %r{<p><a class="user active".*>#{u.name}</a> should not be parsed in <a class="email" href="mailto:jsmith@somenet.foo">jsmith@somenet.foo</a></p>},
  493         textilizable(raw, :project => Project.find(1)))
  494     end
  495   end
  496 
  497   def test_user_links_with_email_as_login_name_should_not_be_parsed_markdown
  498     with_settings :text_formatting => 'markdown' do
  499       u = User.generate!(:login => 'jsmith@somenet.foo')
  500 
  501       # user link format: @jsmith@somenet.foo
  502       raw = "@jsmith@somenet.foo should not be parsed in jsmith@somenet.foo"
  503       assert_match(
  504         %r{<p><a class=\"user active\".*>#{u.name}</a> should not be parsed in <a href=\"mailto:jsmith@somenet.foo\">jsmith@somenet.foo</a></p>},
  505         textilizable(raw, :project => Project.find(1)))
  506 
  507       # user link format: user:jsmith@somenet.foo
  508       raw = "user:jsmith@somenet.foo should not be parsed in jsmith@somenet.foo"
  509       assert_match(
  510         %r{<p><a class=\"user active\".*>#{u.name}</a> should not be parsed in <a href=\"mailto:jsmith@somenet.foo\">jsmith@somenet.foo</a></p>},
  511         textilizable(raw, :project => Project.find(1)))
  512     end
  513   end
  514 
  515   def test_should_not_parse_redmine_links_inside_link
  516     raw = "r1 should not be parsed in http://example.com/url-r1/"
  517     assert_match(
  518       %r{<p><a class="changeset".*>r1</a> should not be parsed in <a class="external" href="http://example.com/url-r1/">http://example.com/url-r1/</a></p>},
  519       textilizable(raw, :project => Project.find(1)))
  520   end
  521 
  522   def test_redmine_links_with_a_different_project_before_current_project
  523     vp1 = Version.generate!(:project_id => 1, :name => '1.4.4')
  524     vp3 = Version.generate!(:project_id => 3, :name => '1.4.4')
  525     @project = Project.find(3)
  526     result1 = link_to("1.4.4", "/versions/#{vp1.id}", :class => "version")
  527     result2 = link_to("1.4.4", "/versions/#{vp3.id}", :class => "version")
  528     assert_equal "<p>#{result1} #{result2}</p>",
  529                  textilizable("ecookbook:version:1.4.4 version:1.4.4")
  530   end
  531 
  532   def test_escaped_redmine_links_should_not_be_parsed
  533     to_test = [
  534       '#3.',
  535       '#3-14.',
  536       '#3#-note14.',
  537       'r1',
  538       'document#1',
  539       'document:"Test document"',
  540       'version#2',
  541       'version:1.0',
  542       'version:"1.0"',
  543       'source:/some/file'
  544     ]
  545     @project = Project.find(1)
  546     to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
  547   end
  548 
  549   def test_cross_project_redmine_links
  550     source_link = link_to('ecookbook:source:/some/file',
  551                           {:controller => 'repositories', :action => 'entry',
  552                            :id => 'ecookbook', :repository_id => 10, :path => ['some', 'file']},
  553                           :class => 'source')
  554     changeset_link = link_to('ecookbook:r2',
  555                              {:controller => 'repositories', :action => 'revision',
  556                               :id => 'ecookbook', :repository_id => 10, :rev => 2},
  557                              :class => 'changeset',
  558                              :title => 'This commit fixes #1, #2 and references #1 & #3')
  559     to_test = {
  560       # documents
  561       'document:"Test document"'              => 'document:"Test document"',
  562       'ecookbook:document:"Test document"'    =>
  563           link_to("Test document", "/documents/1", :class => "document"),
  564       'invalid:document:"Test document"'      => 'invalid:document:"Test document"',
  565       # versions
  566       'version:"1.0"'                         => 'version:"1.0"',
  567       'ecookbook:version:"1.0"'               =>
  568           link_to("1.0", "/versions/2", :class => "version"),
  569       'invalid:version:"1.0"'                 => 'invalid:version:"1.0"',
  570       # changeset
  571       'r2'                                    => 'r2',
  572       'ecookbook:r2'                          => changeset_link,
  573       'invalid:r2'                            => 'invalid:r2',
  574       # source
  575       'source:/some/file'                     => 'source:/some/file',
  576       'ecookbook:source:/some/file'           => source_link,
  577       'invalid:source:/some/file'             => 'invalid:source:/some/file',
  578     }
  579     @project = Project.find(3)
  580     to_test.each do |text, result|
  581       assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed"
  582     end
  583   end
  584 
  585   def test_redmine_links_by_name_should_work_with_html_escaped_characters
  586     v = Version.generate!(:name => "Test & Show.txt", :project_id => 1)
  587     link = link_to("Test & Show.txt", "/versions/#{v.id}", :class => "version")
  588 
  589     @project = v.project
  590     assert_equal "<p>#{link}</p>", textilizable('version:"Test & Show.txt"')
  591   end
  592 
  593   def test_link_to_issue_subject
  594     issue = Issue.generate!(:subject => "01234567890123456789")
  595     str = link_to_issue(issue, :truncate => 10)
  596     result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
  597     assert_equal "#{result}: 0123456...", str
  598 
  599     issue = Issue.generate!(:subject => "<&>")
  600     str = link_to_issue(issue)
  601     result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
  602     assert_equal "#{result}: &lt;&amp;&gt;", str
  603 
  604     issue = Issue.generate!(:subject => "<&>0123456789012345")
  605     str = link_to_issue(issue, :truncate => 10)
  606     result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
  607     assert_equal "#{result}: &lt;&amp;&gt;0123...", str
  608   end
  609 
  610   def test_link_to_issue_title
  611     long_str = "0123456789" * 5
  612 
  613     issue = Issue.generate!(:subject => "#{long_str}01234567890123456789")
  614     str = link_to_issue(issue, :subject => false)
  615     result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
  616                      :class => issue.css_classes,
  617                      :title => "#{long_str}0123456...")
  618     assert_equal result, str
  619 
  620     issue = Issue.generate!(:subject => "<&>#{long_str}01234567890123456789")
  621     str = link_to_issue(issue, :subject => false)
  622     result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
  623                      :class => issue.css_classes,
  624                      :title => "<&>#{long_str}0123...")
  625     assert_equal result, str
  626   end
  627 
  628   def test_multiple_repositories_redmine_links
  629     svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg')
  630     Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
  631     hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
  632     Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
  633 
  634     changeset_link = link_to(
  635                        'r2',
  636                        {:controller => 'repositories', :action => 'revision',
  637                         :id => 'ecookbook', :repository_id => 10, :rev => 2},
  638                        :class => 'changeset',
  639                        :title => 'This commit fixes #1, #2 and references #1 & #3')
  640     svn_changeset_link = link_to(
  641                            'svn_repo-1|r123',
  642                            {:controller => 'repositories', :action => 'revision',
  643                             :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123},
  644                            :class => 'changeset', :title => '')
  645     hg_changeset_link = link_to(
  646                           'hg1|abcd',
  647                           {:controller => 'repositories', :action => 'revision',
  648                            :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
  649                           :class => 'changeset', :title => '')
  650     source_link = link_to('source:some/file',
  651                           {:controller => 'repositories', :action => 'entry',
  652                            :id => 'ecookbook', :repository_id => 10,
  653                            :path => ['some', 'file']},
  654                           :class => 'source')
  655     hg_source_link = link_to('source:hg1|some/file',
  656                              {:controller => 'repositories', :action => 'entry',
  657                               :id => 'ecookbook', :repository_id => 'hg1',
  658                               :path => ['some', 'file']},
  659                              :class => 'source')
  660 
  661     to_test = {
  662       'r2'                          => changeset_link,
  663       'svn_repo-1|r123'             => svn_changeset_link,
  664       'invalid|r123'                => 'invalid|r123',
  665       'commit:hg1|abcd'             => hg_changeset_link,
  666       'commit:invalid|abcd'         => 'commit:invalid|abcd',
  667       # source
  668       'source:some/file'            => source_link,
  669       'source:hg1|some/file'        => hg_source_link,
  670       'source:invalid|some/file'    => 'source:invalid|some/file',
  671     }
  672 
  673     @project = Project.find(1)
  674     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
  675   end
  676 
  677   def test_cross_project_multiple_repositories_redmine_links
  678     svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
  679     Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
  680     hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
  681     Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
  682 
  683     changeset_link = link_to(
  684                        'ecookbook:r2',
  685                        {:controller => 'repositories', :action => 'revision',
  686                         :id => 'ecookbook', :repository_id => 10, :rev => 2},
  687                        :class => 'changeset',
  688                        :title => 'This commit fixes #1, #2 and references #1 & #3')
  689     svn_changeset_link = link_to(
  690                            'ecookbook:svn1|r123',
  691                            {:controller => 'repositories', :action => 'revision',
  692                             :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
  693                            :class => 'changeset', :title => '')
  694     hg_changeset_link = link_to(
  695                           'ecookbook:hg1|abcd',
  696                           {:controller => 'repositories', :action => 'revision',
  697                            :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
  698                           :class => 'changeset', :title => '')
  699 
  700     source_link = link_to('ecookbook:source:some/file',
  701                           {:controller => 'repositories', :action => 'entry',
  702                            :id => 'ecookbook', :repository_id => 10,
  703                            :path => ['some', 'file']}, :class => 'source')
  704     hg_source_link = link_to('ecookbook:source:hg1|some/file',
  705                              {:controller => 'repositories', :action => 'entry',
  706                               :id => 'ecookbook', :repository_id => 'hg1',
  707                               :path => ['some', 'file']}, :class => 'source')
  708     to_test = {
  709       'ecookbook:r2'                           => changeset_link,
  710       'ecookbook:svn1|r123'                    => svn_changeset_link,
  711       'ecookbook:invalid|r123'                 => 'ecookbook:invalid|r123',
  712       'ecookbook:commit:hg1|abcd'              => hg_changeset_link,
  713       'ecookbook:commit:invalid|abcd'          => 'ecookbook:commit:invalid|abcd',
  714       'invalid:commit:invalid|abcd'            => 'invalid:commit:invalid|abcd',
  715       # source
  716       'ecookbook:source:some/file'             => source_link,
  717       'ecookbook:source:hg1|some/file'         => hg_source_link,
  718       'ecookbook:source:invalid|some/file'     => 'ecookbook:source:invalid|some/file',
  719       'invalid:source:invalid|some/file'       => 'invalid:source:invalid|some/file',
  720     }
  721     @project = Project.find(3)
  722     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
  723   end
  724 
  725   def test_redmine_links_git_commit
  726     @project = Project.find(3)
  727     r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
  728     c = Changeset.create!(
  729                       :repository => r,
  730                       :committed_on => Time.now,
  731                       :revision => 'abcd',
  732                       :scmid => 'abcd',
  733                       :comments => 'test commit')
  734     changeset_link = link_to('abcd',
  735                              {
  736                                  :controller => 'repositories',
  737                                  :action     => 'revision',
  738                                  :id         => 'subproject1',
  739                                  :repository_id => r.id,
  740                                  :rev        => 'abcd',
  741                               },
  742                              :class => 'changeset', :title => 'test commit')
  743     to_test = {
  744       'commit:abcd' => changeset_link,
  745      }
  746     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  747   end
  748 
  749   # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
  750   def test_redmine_links_mercurial_commit
  751     @project = Project.find(3)
  752     r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
  753     c = Changeset.create!(
  754                       :repository => r,
  755                       :committed_on => Time.now,
  756                       :revision => '123',
  757                       :scmid => 'abcd',
  758                       :comments => 'test commit')
  759     changeset_link_rev = link_to(
  760                               'r123',
  761                               {
  762                                      :controller => 'repositories',
  763                                      :action     => 'revision',
  764                                      :id         => 'subproject1',
  765                                      :repository_id => r.id,
  766                                      :rev        => '123',
  767                               },
  768                               :class => 'changeset', :title => 'test commit')
  769     changeset_link_commit = link_to(
  770                               'abcd',
  771                               {
  772                                     :controller => 'repositories',
  773                                     :action     => 'revision',
  774                                     :id         => 'subproject1',
  775                                     :repository_id => r.id,
  776                                     :rev        => 'abcd',
  777                               },
  778                               :class => 'changeset', :title => 'test commit')
  779     to_test = {
  780       'r123' => changeset_link_rev,
  781       'commit:abcd' => changeset_link_commit,
  782      }
  783     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  784   end
  785 
  786   def test_attachment_links
  787     text = 'attachment:error281.txt'
  788     result = link_to("error281.txt", "/attachments/1",
  789                      :class => "attachment")
  790     assert_equal "<p>#{result}</p>",
  791                  textilizable(text,
  792                               :attachments => Issue.find(3).attachments),
  793                  "#{text} failed"
  794   end
  795 
  796   def test_attachment_link_should_link_to_latest_attachment
  797     a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago)
  798     a2 = Attachment.generate!(:filename => "test.txt")
  799     result = link_to("test.txt", "/attachments/#{a2.id}",
  800                      :class => "attachment")
  801     assert_equal "<p>#{result}</p>",
  802                  textilizable('attachment:test.txt', :attachments => [a1, a2])
  803   end
  804 
  805   def test_attachment_links_to_images_with_email_format_should_not_be_parsed
  806     attachment = Attachment.generate!(:filename => 'image@2x.png')
  807     with_settings :text_formatting => 'textile' do
  808       raw = "attachment:image@2x.png should not be parsed in image@2x.png"
  809       assert_match(
  810         %r{<p><a class="attachment" href="/attachments/#{attachment.id}">image@2x.png</a> should not be parsed in image@2x.png</p>},
  811         textilizable(raw, :attachments => [attachment]))
  812     end
  813     with_settings :text_formatting => 'markdown' do
  814       raw = "attachment:image@2x.png should not be parsed in image@2x.png"
  815       assert_match(
  816         %r{<p><a class="attachment" href="/attachments/#{attachment.id}">image@2x.png</a> should not be parsed in image@2x.png</p>},
  817         textilizable(raw, :attachments => [attachment]))
  818     end
  819   end
  820 
  821   def test_wiki_links
  822     User.current = User.find_by_login('jsmith')
  823     russian_eacape = CGI.escape(@russian_test)
  824     to_test = {
  825       '[[CookBook documentation]]' =>
  826           link_to("CookBook documentation",
  827                   "/projects/ecookbook/wiki/CookBook_documentation",
  828                   :class => "wiki-page"),
  829       '[[Another page|Page]]' =>
  830           link_to("Page",
  831                   "/projects/ecookbook/wiki/Another_page",
  832                   :class => "wiki-page"),
  833       # title content should be formatted
  834       '[[Another page|With _styled_ *title*]]' =>
  835           link_to("With <em>styled</em> <strong>title</strong>".html_safe,
  836                   "/projects/ecookbook/wiki/Another_page",
  837                   :class => "wiki-page"),
  838       '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' =>
  839           link_to("With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;".html_safe,
  840                   "/projects/ecookbook/wiki/Another_page",
  841                   :class => "wiki-page"),
  842       # link with anchor
  843       '[[CookBook documentation#One-section]]' =>
  844           link_to("CookBook documentation",
  845                   "/projects/ecookbook/wiki/CookBook_documentation#One-section",
  846                   :class => "wiki-page"),
  847       '[[Another page#anchor|Page]]' =>
  848           link_to("Page",
  849                   "/projects/ecookbook/wiki/Another_page#anchor",
  850                   :class => "wiki-page"),
  851       # UTF8 anchor
  852       "[[Another_page##{@russian_test}|#{@russian_test}]]" =>
  853           link_to(@russian_test,
  854                   "/projects/ecookbook/wiki/Another_page##{russian_eacape}",
  855                   :class => "wiki-page"),
  856       # link to anchor
  857       '[[#anchor]]' =>
  858           link_to("#anchor",
  859                   "#anchor",
  860                   :class => "wiki-page"),
  861       '[[#anchor|One-section]]' =>
  862           link_to("One-section",
  863                   "#anchor",
  864                   :class => "wiki-page"),
  865       # page that doesn't exist
  866       '[[Unknown page]]' =>
  867           link_to("Unknown page",
  868                   "/projects/ecookbook/wiki/Unknown_page",
  869                   :class => "wiki-page new"),
  870       '[[Unknown page|404]]' =>
  871           link_to("404",
  872                   "/projects/ecookbook/wiki/Unknown_page",
  873                   :class => "wiki-page new"),
  874       # link to another project wiki
  875       '[[onlinestore:]]' =>
  876           link_to("onlinestore",
  877                   "/projects/onlinestore/wiki",
  878                   :class => "wiki-page"),
  879       '[[onlinestore:|Wiki]]' =>
  880           link_to("Wiki",
  881                   "/projects/onlinestore/wiki",
  882                   :class => "wiki-page"),
  883       '[[onlinestore:Start page]]' =>
  884           link_to("Start page",
  885                   "/projects/onlinestore/wiki/Start_page",
  886                   :class => "wiki-page"),
  887       '[[onlinestore:Start page|Text]]' =>
  888           link_to("Text",
  889                   "/projects/onlinestore/wiki/Start_page",
  890                   :class => "wiki-page"),
  891       '[[onlinestore:Unknown page]]' =>
  892           link_to("Unknown page",
  893                   "/projects/onlinestore/wiki/Unknown_page",
  894                   :class => "wiki-page new"),
  895       # struck through link
  896       '-[[Another page|Page]]-' =>
  897           "<del>".html_safe +
  898             link_to("Page",
  899                     "/projects/ecookbook/wiki/Another_page",
  900                     :class => "wiki-page").html_safe +
  901             "</del>".html_safe,
  902       '-[[Another page|Page]] link-' =>
  903           "<del>".html_safe +
  904             link_to("Page",
  905                     "/projects/ecookbook/wiki/Another_page",
  906                     :class => "wiki-page").html_safe +
  907             " link</del>".html_safe,
  908       # escaping
  909       '![[Another page|Page]]' => '[[Another page|Page]]',
  910       # project does not exist
  911       '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
  912       '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
  913       # missing permission to view wiki in project
  914       '[[private-child:]]' => '[[private-child:]]',
  915       '[[private-child:Wiki]]' => '[[private-child:Wiki]]',
  916     }
  917     @project = Project.find(1)
  918     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  919   end
  920 
  921   def test_wiki_links_with_special_characters_should_work_in_textile
  922     to_test = wiki_links_with_special_characters
  923 
  924     @project = Project.find(1)
  925     with_settings :text_formatting => 'textile' do
  926       to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  927     end
  928   end
  929 
  930   def test_wiki_links_with_special_characters_should_work_in_markdown
  931     to_test = wiki_links_with_special_characters
  932 
  933     @project = Project.find(1)
  934     with_settings :text_formatting => 'markdown' do
  935       to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text).strip }
  936     end
  937   end
  938 
  939   def test_wiki_links_with_square_brackets_in_project_name
  940     User.current = User.find_by_login('jsmith')
  941 
  942     another_project = Project.find(1) # eCookbook
  943     another_project.name = "[foo]#{another_project.name}"
  944     another_project.save
  945 
  946     page = another_project.wiki.find_page('Another page')
  947     page.title = "[bar]#{page.title}"
  948     page.save
  949 
  950     to_test = {
  951       '[[[foo]eCookbook:]]' =>
  952           link_to("[foo]eCookbook",
  953                   "/projects/ecookbook/wiki",
  954                   :class => "wiki-page"),
  955       '[[[foo]eCookbook:CookBook documentation]]' =>
  956           link_to("CookBook documentation",
  957                   "/projects/ecookbook/wiki/CookBook_documentation",
  958                   :class => "wiki-page"),
  959       '[[[foo]eCookbook:[bar]Another page]]' =>
  960           link_to("[bar]Another page",
  961                   "/projects/ecookbook/wiki/%5Bbar%5DAnother_page",
  962                   :class => "wiki-page"),
  963       '[[[foo]eCookbook:Unknown page]]' =>
  964           link_to("Unknown page",
  965                   "/projects/ecookbook/wiki/Unknown_page",
  966                   :class => "wiki-page new"),
  967       '[[[foo]eCookbook:[baz]Unknown page]]' =>
  968           link_to("[baz]Unknown page",
  969                   "/projects/ecookbook/wiki/%5Bbaz%5DUnknown_page",
  970                   :class => "wiki-page new"),
  971     }
  972     @project = Project.find(2)  # OnlineStore
  973     with_settings :text_formatting => 'textile' do
  974       to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
  975     end
  976     with_settings :text_formatting => 'markdown' do
  977       to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text).strip }
  978     end
  979   end
  980 
  981   def test_wiki_links_within_local_file_generation_context
  982     to_test = {
  983       # link to a page
  984       '[[CookBook documentation]]' =>
  985          link_to("CookBook documentation", "CookBook_documentation.html",
  986                  :class => "wiki-page"),
  987       '[[CookBook documentation|documentation]]' =>
  988          link_to("documentation", "CookBook_documentation.html",
  989                  :class => "wiki-page"),
  990       '[[CookBook documentation#One-section]]' =>
  991          link_to("CookBook documentation", "CookBook_documentation.html#One-section",
  992                  :class => "wiki-page"),
  993       '[[CookBook documentation#One-section|documentation]]' =>
  994          link_to("documentation", "CookBook_documentation.html#One-section",
  995                  :class => "wiki-page"),
  996       # page that doesn't exist
  997       '[[Unknown page]]' =>
  998          link_to("Unknown page", "Unknown_page.html",
  999                  :class => "wiki-page new"),
 1000       '[[Unknown page|404]]' =>
 1001          link_to("404", "Unknown_page.html",
 1002                  :class => "wiki-page new"),
 1003       '[[Unknown page#anchor]]' =>
 1004          link_to("Unknown page", "Unknown_page.html#anchor",
 1005                  :class => "wiki-page new"),
 1006       '[[Unknown page#anchor|404]]' =>
 1007          link_to("404", "Unknown_page.html#anchor",
 1008                  :class => "wiki-page new"),
 1009     }
 1010     @project = Project.find(1)
 1011     to_test.each do |text, result|
 1012       assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local)
 1013     end
 1014   end
 1015 
 1016   def test_wiki_links_within_wiki_page_context
 1017     page = WikiPage.find_by_title('Another_page' )
 1018     to_test = {
 1019       '[[CookBook documentation]]' =>
 1020          link_to("CookBook documentation",
 1021                  "/projects/ecookbook/wiki/CookBook_documentation",
 1022                  :class => "wiki-page"),
 1023       '[[CookBook documentation|documentation]]' =>
 1024          link_to("documentation",
 1025                  "/projects/ecookbook/wiki/CookBook_documentation",
 1026                  :class => "wiki-page"),
 1027       '[[CookBook documentation#One-section]]' =>
 1028          link_to("CookBook documentation",
 1029                  "/projects/ecookbook/wiki/CookBook_documentation#One-section",
 1030                  :class => "wiki-page"),
 1031       '[[CookBook documentation#One-section|documentation]]' =>
 1032          link_to("documentation",
 1033                  "/projects/ecookbook/wiki/CookBook_documentation#One-section",
 1034                  :class => "wiki-page"),
 1035       # link to the current page
 1036       '[[Another page]]' =>
 1037          link_to("Another page",
 1038                  "/projects/ecookbook/wiki/Another_page",
 1039                  :class => "wiki-page"),
 1040       '[[Another page|Page]]' =>
 1041          link_to("Page",
 1042                  "/projects/ecookbook/wiki/Another_page",
 1043                  :class => "wiki-page"),
 1044       '[[Another page#anchor]]' =>
 1045          link_to("Another page",
 1046                  "#anchor",
 1047                  :class => "wiki-page"),
 1048       '[[Another page#anchor|Page]]' =>
 1049          link_to("Page",
 1050                  "#anchor",
 1051                  :class => "wiki-page"),
 1052       # page that doesn't exist
 1053       '[[Unknown page]]' =>
 1054          link_to("Unknown page",
 1055                  "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
 1056                  :class => "wiki-page new"),
 1057       '[[Unknown page|404]]' =>
 1058          link_to("404",
 1059                  "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
 1060                  :class => "wiki-page new"),
 1061       '[[Unknown page#anchor]]' =>
 1062          link_to("Unknown page",
 1063                  "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
 1064                  :class => "wiki-page new"),
 1065       '[[Unknown page#anchor|404]]' =>
 1066          link_to("404",
 1067                  "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
 1068                  :class => "wiki-page new"),
 1069     }
 1070     @project = Project.find(1)
 1071     to_test.each do |text, result|
 1072       assert_equal "<p>#{result}</p>",
 1073                    textilizable(WikiContent.new( :text => text, :page => page ), :text)
 1074     end
 1075   end
 1076 
 1077   def test_wiki_links_anchor_option_should_prepend_page_title_to_href
 1078     to_test = {
 1079       # link to a page
 1080       '[[CookBook documentation]]' =>
 1081           link_to("CookBook documentation",
 1082                   "#CookBook_documentation",
 1083                   :class => "wiki-page"),
 1084       '[[CookBook documentation|documentation]]' =>
 1085           link_to("documentation",
 1086                   "#CookBook_documentation",
 1087                   :class => "wiki-page"),
 1088       '[[CookBook documentation#One-section]]' =>
 1089           link_to("CookBook documentation",
 1090                   "#CookBook_documentation_One-section",
 1091                   :class => "wiki-page"),
 1092       '[[CookBook documentation#One-section|documentation]]' =>
 1093           link_to("documentation",
 1094                   "#CookBook_documentation_One-section",
 1095                   :class => "wiki-page"),
 1096       # page that doesn't exist
 1097       '[[Unknown page]]' =>
 1098           link_to("Unknown page",
 1099                   "#Unknown_page",
 1100                   :class => "wiki-page new"),
 1101       '[[Unknown page|404]]' =>
 1102           link_to("404",
 1103                   "#Unknown_page",
 1104                   :class => "wiki-page new"),
 1105       '[[Unknown page#anchor]]' =>
 1106           link_to("Unknown page",
 1107                   "#Unknown_page_anchor",
 1108                   :class => "wiki-page new"),
 1109       '[[Unknown page#anchor|404]]' =>
 1110           link_to("404",
 1111                   "#Unknown_page_anchor",
 1112                   :class => "wiki-page new"),
 1113     }
 1114     @project = Project.find(1)
 1115     to_test.each do |text, result|
 1116       assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor)
 1117     end
 1118   end
 1119 
 1120   def test_html_tags
 1121     to_test = {
 1122       "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
 1123       "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
 1124       "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
 1125       # do not escape pre/code tags
 1126       "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
 1127       "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
 1128       "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
 1129       "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
 1130       "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
 1131       # remove attributes including class
 1132       "<pre class='foo'>some text</pre>" => "<pre>some text</pre>",
 1133       '<pre class="foo">some text</pre>' => '<pre>some text</pre>',
 1134       "<pre class='foo bar'>some text</pre>" => "<pre>some text</pre>",
 1135       '<pre class="foo bar">some text</pre>' => '<pre>some text</pre>',
 1136       "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
 1137       # xss
 1138       '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
 1139       '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
 1140     }
 1141     to_test.each { |text, result| assert_equal result, textilizable(text) }
 1142   end
 1143 
 1144   def test_allowed_html_tags
 1145     to_test = {
 1146       "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
 1147       "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
 1148       "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
 1149     }
 1150     to_test.each { |text, result| assert_equal result, textilizable(text) }
 1151   end
 1152 
 1153   def test_pre_tags
 1154     raw = <<~RAW
 1155       Before
 1156 
 1157       <pre>
 1158       <prepared-statement-cache-size>32</prepared-statement-cache-size>
 1159       </pre>
 1160 
 1161       After
 1162     RAW
 1163     expected = <<~EXPECTED
 1164       <p>Before</p>
 1165       <pre>
 1166       &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
 1167       </pre>
 1168       <p>After</p>
 1169     EXPECTED
 1170     assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
 1171   end
 1172 
 1173   def test_pre_content_should_not_parse_wiki_and_redmine_links
 1174     raw = <<~RAW
 1175       [[CookBook documentation]]
 1176 
 1177       #1
 1178 
 1179       <pre>
 1180       [[CookBook documentation]]
 1181 
 1182       #1
 1183       </pre>
 1184     RAW
 1185     result1 = link_to("CookBook documentation",
 1186                       "/projects/ecookbook/wiki/CookBook_documentation",
 1187                       :class => "wiki-page")
 1188     result2 = link_to('#1',
 1189                       "/issues/1",
 1190                       :class => Issue.find(1).css_classes,
 1191                       :title => "Bug: Cannot print recipes (New)")
 1192     expected = <<~EXPECTED
 1193       <p>#{result1}</p>
 1194       <p>#{result2}</p>
 1195       <pre>
 1196       [[CookBook documentation]]
 1197 
 1198       #1
 1199       </pre>
 1200     EXPECTED
 1201     @project = Project.find(1)
 1202     assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
 1203   end
 1204 
 1205   def test_non_closing_pre_blocks_should_be_closed
 1206     raw = <<~RAW
 1207       <pre><code>
 1208     RAW
 1209     expected = <<~EXPECTED
 1210       <pre><code>
 1211       </code></pre>
 1212     EXPECTED
 1213     @project = Project.find(1)
 1214     assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
 1215   end
 1216 
 1217   def test_unbalanced_closing_pre_tag_should_not_error
 1218     assert_nothing_raised do
 1219       textilizable("unbalanced</pre>")
 1220     end
 1221   end
 1222 
 1223   def test_syntax_highlight
 1224     raw = <<~RAW
 1225       <pre><code class="ECMA_script">
 1226       /* Hello */
 1227       document.write("Hello World!");
 1228       </code></pre>
 1229     RAW
 1230     expected = <<~EXPECTED
 1231       <pre><code class="ECMA_script syntaxhl"><span class="cm">/* Hello */</span><span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello World!</span><span class="dl">"</span><span class="p">);</span></code></pre>
 1232     EXPECTED
 1233     assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
 1234   end
 1235 
 1236   def test_syntax_highlight_ampersand_in_textile
 1237     raw = <<~RAW
 1238       <pre><code class="ruby">
 1239       x = a & b
 1240       </code></pre>
 1241     RAW
 1242     expected = <<~EXPECTED
 1243       <pre><code class=\"ruby syntaxhl\"><span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">a</span> <span class=\"o\">&amp;</span> <span class=\"n\">b</span></code></pre>
 1244     EXPECTED
 1245     with_settings :text_formatting => 'textile' do
 1246       assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
 1247     end
 1248   end
 1249 
 1250   def test_syntax_highlight_should_normalize_line_endings
 1251     assert_equal "line 1\nline 2\n", syntax_highlight("test.txt", "line 1\rline 2\r\n")
 1252   end
 1253 
 1254   def test_to_path_param
 1255     assert_equal 'test1/test2', to_path_param('test1/test2')
 1256     assert_equal 'test1/test2', to_path_param('/test1/test2/')
 1257     assert_equal 'test1/test2', to_path_param('//test1/test2/')
 1258     assert_nil to_path_param('/')
 1259   end
 1260 
 1261   def test_wiki_links_in_tables
 1262     text = "|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|"
 1263     link1 = link_to("Link title", "/projects/ecookbook/wiki/Page", :class => "wiki-page new")
 1264     link2 = link_to("Other title", "/projects/ecookbook/wiki/Other_Page", :class => "wiki-page new")
 1265     link3 = link_to("Last page", "/projects/ecookbook/wiki/Last_page", :class => "wiki-page new")
 1266     result = "<tr><td>#{link1}</td>" +
 1267                "<td>#{link2}</td>" +
 1268                "</tr><tr><td>Cell 21</td><td>#{link3}</td></tr>"
 1269     @project = Project.find(1)
 1270     assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '')
 1271   end
 1272 
 1273   def test_text_formatting
 1274     to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
 1275                '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
 1276                'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
 1277                '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>',
 1278                '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',
 1279               }
 1280     to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
 1281   end
 1282 
 1283   def test_wiki_horizontal_rule
 1284     assert_equal '<hr />', textilizable('---')
 1285     assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
 1286   end
 1287 
 1288   def test_headings
 1289     raw = 'h1. Some heading'
 1290     expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
 1291 
 1292     assert_equal expected, textilizable(raw)
 1293   end
 1294 
 1295   def test_headings_with_special_chars
 1296     # This test makes sure that the generated anchor names match the expected
 1297     # ones even if the heading text contains unconventional characters
 1298     raw = 'h1. Some heading related to version 0.5'
 1299     anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
 1300     expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
 1301 
 1302     assert_equal expected, textilizable(raw)
 1303   end
 1304 
 1305   def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
 1306     page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
 1307     content = WikiContent.new( :text => 'h1. Some heading', :page => page )
 1308 
 1309     expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
 1310 
 1311     assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
 1312   end
 1313 
 1314   def test_table_of_content
 1315     set_language_if_valid 'en'
 1316     raw = <<~RAW
 1317       {{toc}}
 1318 
 1319       h1. Title
 1320 
 1321       Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
 1322 
 1323       h2. Subtitle with a [[Wiki]] link
 1324 
 1325       Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
 1326 
 1327       h2. Subtitle with [[Wiki|another Wiki]] link
 1328 
 1329       h2. Subtitle with %{color:red}red text%
 1330 
 1331       <pre>
 1332       some code
 1333       </pre>
 1334 
 1335       h3. Subtitle with *some* _modifiers_
 1336 
 1337       h3. Subtitle with @inline code@
 1338 
 1339       h1. Another title
 1340 
 1341       h3. An "Internet link":http://www.redmine.org/ inside subtitle
 1342 
 1343       h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
 1344 
 1345     RAW
 1346     expected =  '<ul class="toc">' +
 1347                   '<li><strong>Table of contents</strong></li>' +
 1348                   '<li><a href="#Title">Title</a>' +
 1349                     '<ul>' +
 1350                       '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
 1351                       '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
 1352                       '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
 1353                         '<ul>' +
 1354                           '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
 1355                           '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
 1356                         '</ul>' +
 1357                       '</li>' +
 1358                     '</ul>' +
 1359                   '</li>' +
 1360                   '<li><a href="#Another-title">Another title</a>' +
 1361                     '<ul>' +
 1362                       '<li>' +
 1363                         '<ul>' +
 1364                           '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
 1365                         '</ul>' +
 1366                       '</li>' +
 1367                       '<li><a href="#Project-Name">Project Name</a></li>' +
 1368                     '</ul>' +
 1369                   '</li>' +
 1370                '</ul>'
 1371 
 1372     @project = Project.find(1)
 1373     assert textilizable(raw).gsub("\n", "").include?(expected)
 1374   end
 1375 
 1376   def test_table_of_content_should_generate_unique_anchors
 1377     set_language_if_valid 'en'
 1378     raw = <<~RAW
 1379       {{toc}}
 1380 
 1381       h1. Title
 1382 
 1383       h2. Subtitle
 1384 
 1385       h2. Subtitle
 1386     RAW
 1387     expected =  '<ul class="toc">' +
 1388                   '<li><strong>Table of contents</strong></li>' +
 1389                   '<li><a href="#Title">Title</a>' +
 1390                     '<ul>' +
 1391                       '<li><a href="#Subtitle">Subtitle</a></li>' +
 1392                       '<li><a href="#Subtitle-2">Subtitle</a></li>' +
 1393                     '</ul>' +
 1394                   '</li>' +
 1395                 '</ul>'
 1396     @project = Project.find(1)
 1397     result = textilizable(raw).gsub("\n", "")
 1398     assert_include expected, result
 1399     assert_include '<a name="Subtitle">', result
 1400     assert_include '<a name="Subtitle-2">', result
 1401   end
 1402 
 1403   def test_table_of_content_should_contain_included_page_headings
 1404     set_language_if_valid 'en'
 1405     raw = <<~RAW
 1406       {{toc}}
 1407 
 1408       h1. Included
 1409 
 1410       {{include(Child_1)}}
 1411     RAW
 1412     expected = '<ul class="toc">' +
 1413                '<li><strong>Table of contents</strong></li>' +
 1414                '<li><a href="#Included">Included</a></li>' +
 1415                '<li><a href="#Child-page-1">Child page 1</a></li>' +
 1416                '</ul>'
 1417     @project = Project.find(1)
 1418     assert textilizable(raw).gsub("\n", "").include?(expected)
 1419   end
 1420 
 1421   def test_toc_with_textile_formatting_should_be_parsed
 1422     with_settings :text_formatting => 'textile' do
 1423       assert_select_in textilizable("{{toc}}\n\nh1. Heading"), 'ul.toc li', :text => 'Heading'
 1424       assert_select_in textilizable("{{<toc}}\n\nh1. Heading"), 'ul.toc.left li', :text => 'Heading'
 1425       assert_select_in textilizable("{{>toc}}\n\nh1. Heading"), 'ul.toc.right li', :text => 'Heading'
 1426     end
 1427   end
 1428 
 1429   if Object.const_defined?(:Redcarpet)
 1430   def test_toc_with_markdown_formatting_should_be_parsed
 1431     with_settings :text_formatting => 'markdown' do
 1432       assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
 1433       assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
 1434       assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
 1435     end
 1436   end
 1437   end
 1438 
 1439   def test_section_edit_links
 1440     raw = <<~RAW
 1441       h1. Title
 1442 
 1443       Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
 1444 
 1445       h2. Subtitle with a [[Wiki]] link
 1446 
 1447       h2. Subtitle with *some* _modifiers_
 1448 
 1449       h2. Subtitle with @inline code@
 1450 
 1451       <pre>
 1452       some code
 1453 
 1454       h2. heading inside pre
 1455 
 1456       <h2>html heading inside pre</h2>
 1457       </pre>
 1458 
 1459       h2. Subtitle after pre tag
 1460     RAW
 1461     @project = Project.find(1)
 1462     set_language_if_valid 'en'
 1463     result = textilizable(
 1464                raw,
 1465                :edit_section_links =>
 1466                  {:controller => 'wiki', :action => 'edit',
 1467                   :project_id => '1', :id => 'Test'}
 1468              ).gsub("\n", "")
 1469     # heading that contains inline code
 1470     assert_match(
 1471       Regexp.new('<div class="contextual heading-2" title="Edit this section" id="section-4">' +
 1472       '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=4">Edit this section</a></div>' +
 1473       '<a name="Subtitle-with-inline-code"></a>' +
 1474       '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
 1475       result)
 1476     # last heading
 1477     assert_match(
 1478       Regexp.new('<div class="contextual heading-2" title="Edit this section" id="section-5">' +
 1479       '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=5">Edit this section</a></div>' +
 1480       '<a name="Subtitle-after-pre-tag"></a>' +
 1481       '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
 1482       result)
 1483   end
 1484 
 1485   def test_default_formatter
 1486     with_settings :text_formatting => 'unknown' do
 1487       text = 'a *link*: http://www.example.net/'
 1488       assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
 1489     end
 1490   end
 1491 
 1492   def test_textilizable_with_formatting_set_to_false_should_not_format_text
 1493     assert_equal '*text*', textilizable("*text*", :formatting => false)
 1494   end
 1495 
 1496   def test_textilizable_with_formatting_set_to_true_should_format_text
 1497     assert_equal '<p><strong>text</strong></p>', textilizable("*text*", :formatting => true)
 1498   end
 1499 
 1500   def test_parse_redmine_links_should_handle_a_tag_without_attributes
 1501     text = +'<a>http://example.com</a>'
 1502     expected = text.dup
 1503     parse_redmine_links(text, nil, nil, nil, true, {})
 1504     assert_equal expected, text
 1505   end
 1506 
 1507   def test_due_date_distance_in_words
 1508     to_test = { Date.today => 'Due in 0 days',
 1509                 Date.today + 1 => 'Due in 1 day',
 1510                 Date.today + 100 => 'Due in about 3 months',
 1511                 Date.today + 20000 => 'Due in over 54 years',
 1512                 Date.today - 1 => '1 day late',
 1513                 Date.today - 100 => 'about 3 months late',
 1514                 Date.today - 20000 => 'over 54 years late',
 1515                }
 1516     ::I18n.locale = :en
 1517     to_test.each do |date, expected|
 1518       assert_equal expected, due_date_distance_in_words(date)
 1519     end
 1520   end
 1521 
 1522   def test_render_page_hierarchy
 1523     parent_page = WikiPage.find(1)
 1524     child_page = WikiPage.find_by(parent_id: parent_page.id)
 1525     pages_by_parent_id = { nil => [parent_page], parent_page.id => [child_page] }
 1526     result = render_page_hierarchy(pages_by_parent_id, nil)
 1527     assert_select_in(
 1528       result, 'ul.pages-hierarchy li a[href=?]',
 1529       project_wiki_page_path(project_id: parent_page.project,
 1530                              id: parent_page.title, version: nil))
 1531     assert_select_in(
 1532       result, 'ul.pages-hierarchy li ul.pages-hierarchy a[href=?]',
 1533       project_wiki_page_path(project_id: child_page.project,
 1534                              id: child_page.title, version: nil))
 1535   end
 1536 
 1537   def test_render_page_hierarchy_with_timestamp
 1538     parent_page = WikiPage.find(1)
 1539     child_page = WikiPage.find_by(parent_id: parent_page.id)
 1540     pages_by_parent_id = { nil => [parent_page], parent_page.id => [child_page] }
 1541     result = render_page_hierarchy(pages_by_parent_id, nil, :timestamp => true)
 1542     assert_select_in(
 1543       result, 'ul.pages-hierarchy li a[title=?]',
 1544       l(:label_updated_time,
 1545         distance_of_time_in_words(Time.now, parent_page.updated_on)))
 1546     assert_select_in(
 1547       result, 'ul.pages-hierarchy li ul.pages-hierarchy a[title=?]',
 1548       l(:label_updated_time,
 1549         distance_of_time_in_words(Time.now, child_page.updated_on)))
 1550   end
 1551 
 1552   def test_render_page_hierarchy_when_action_is_export
 1553     parent_page = WikiPage.find(1)
 1554     child_page = WikiPage.find_by(parent_id: parent_page.id)
 1555     pages_by_parent_id = { nil => [parent_page], parent_page.id => [child_page] }
 1556 
 1557     # Change controller and action using stub
 1558     controller.stubs(:controller_name).returns('wiki')
 1559     controller.stubs(:action_name).returns("export")
 1560 
 1561     result = render_page_hierarchy(pages_by_parent_id, nil)
 1562     assert_select_in result, 'ul.pages-hierarchy li a[href=?]', "##{parent_page.title}"
 1563     assert_select_in result, 'ul.pages-hierarchy li ul.pages-hierarchy a[href=?]', "##{child_page.title}"
 1564   end
 1565 
 1566   def test_link_to_user
 1567     user = User.find(2)
 1568     result = link_to("John Smith", "/users/2", :class => "user active")
 1569     assert_equal result, link_to_user(user)
 1570   end
 1571 
 1572   def test_link_to_user_should_not_link_to_locked_user
 1573     with_current_user nil do
 1574       user = User.find(5)
 1575       assert user.locked?
 1576       assert_equal 'Dave2 Lopper2', link_to_user(user)
 1577     end
 1578   end
 1579 
 1580   def test_link_to_user_should_link_to_locked_user_if_current_user_is_admin
 1581     with_current_user User.find(1) do
 1582       user = User.find(5)
 1583       assert user.locked?
 1584       result = link_to("Dave2 Lopper2", "/users/5", :class => "user locked")
 1585       assert_equal result, link_to_user(user)
 1586     end
 1587   end
 1588 
 1589   def test_link_to_group_should_return_only_group_name_for_non_admin_users
 1590     User.current = nil
 1591     group = Group.find(10)
 1592     assert_equal "A Team", link_to_group(group)
 1593   end
 1594 
 1595   def test_link_to_group_should_link_to_group_edit_page_for_admin_users
 1596     User.current = User.find(1)
 1597     group = Group.find(10)
 1598     result = link_to("A Team", "/groups/10/edit")
 1599     assert_equal result, link_to_group(group)
 1600   end
 1601 
 1602   def test_link_to_user_should_not_link_to_anonymous
 1603     user = User.anonymous
 1604     assert user.anonymous?
 1605     t = link_to_user(user)
 1606     assert_equal ::I18n.t(:label_user_anonymous), t
 1607   end
 1608 
 1609   def test_link_to_attachment
 1610     a = Attachment.find(3)
 1611     assert_equal(
 1612       '<a href="/attachments/3">logo.gif</a>',
 1613       link_to_attachment(a))
 1614     assert_equal(
 1615       '<a href="/attachments/3">Text</a>',
 1616       link_to_attachment(a, :text => 'Text'))
 1617     result = link_to("logo.gif", "/attachments/3", :class => "foo")
 1618     assert_equal(
 1619       result,
 1620       link_to_attachment(a, :class => 'foo'))
 1621     assert_equal(
 1622       '<a href="/attachments/download/3/logo.gif">logo.gif</a>',
 1623       link_to_attachment(a, :download => true))
 1624     assert_equal(
 1625       '<a href="http://test.host/attachments/3">logo.gif</a>',
 1626       link_to_attachment(a, :only_path => false))
 1627   end
 1628 
 1629   def test_thumbnail_tag
 1630     a = Attachment.find(3)
 1631     assert_select_in(
 1632       thumbnail_tag(a),
 1633       'a[href=?][title=?] img[src=?]',
 1634       "/attachments/3", "logo.gif", "/attachments/thumbnail/3")
 1635   end
 1636 
 1637   def test_link_to_project
 1638     project = Project.find(1)
 1639     assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
 1640                  link_to_project(project)
 1641     assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
 1642                  link_to_project(project, {:only_path => false, :jump => 'blah'})
 1643   end
 1644 
 1645   def test_link_to_project_settings
 1646     project = Project.find(1)
 1647     assert_equal '<a href="/projects/ecookbook/settings">eCookbook</a>', link_to_project_settings(project)
 1648 
 1649     project.status = Project::STATUS_CLOSED
 1650     assert_equal '<a href="/projects/ecookbook">eCookbook</a>', link_to_project_settings(project)
 1651 
 1652     project.status = Project::STATUS_ARCHIVED
 1653     assert_equal 'eCookbook', link_to_project_settings(project)
 1654   end
 1655 
 1656   def test_link_to_legacy_project_with_numerical_identifier_should_use_id
 1657     # numeric identifier are no longer allowed
 1658     Project.where(:id => 1).update_all(:identifier => 25)
 1659     assert_equal '<a href="/projects/1">eCookbook</a>',
 1660                  link_to_project(Project.find(1))
 1661   end
 1662 
 1663   def test_link_to_record
 1664     [
 1665       [custom_values(:custom_values_007), '<a href="/projects/ecookbook">eCookbook</a>'],
 1666       [documents(:documents_001),         '<a href="/documents/1">Test document</a>'],
 1667       [Group.find(10),                    '<a href="/groups/10">A Team</a>'],
 1668       [issues(:issues_001),               link_to_issue(issues(:issues_001), :subject => false)],
 1669       [messages(:messages_001),           '<a href="/boards/1/topics/1">First post</a>'],
 1670       [news(:news_001),                   '<a href="/news/1">eCookbook first release !</a>'],
 1671       [projects(:projects_001),           '<a href="/projects/ecookbook">eCookbook</a>'],
 1672       [users(:users_001),                 '<a class="user active" href="/users/1">Redmine Admin</a>'],
 1673       [versions(:versions_001),           '<a title="07/01/2006" href="/versions/1">eCookbook - 0.1</a>'],
 1674       [wiki_pages(:wiki_pages_001),       '<a href="/projects/ecookbook/wiki/CookBook_documentation">CookBook documentation</a>']
 1675     ].each do |record, link|
 1676       assert_equal link, link_to_record(record)
 1677     end
 1678   end
 1679 
 1680   def test_link_to_attachment_container
 1681     field = ProjectCustomField.generate!(:name => "File", :field_format => 'attachment')
 1682     project = projects(:projects_001)
 1683     project_custom_value_attachment = new_record(Attachment) do
 1684       project.custom_field_values = {field.id => {:file => mock_file}}
 1685       project.save
 1686     end
 1687 
 1688     news_attachment = attachments(:attachments_004)
 1689     news_attachment.container = news(:news_001)
 1690     news_attachment.save!
 1691 
 1692     [
 1693       [project_custom_value_attachment, '<a href="/projects/ecookbook">eCookbook</a>'],
 1694       [attachments(:attachments_002),   '<a href="/documents/1">Test document</a>'],
 1695       [attachments(:attachments_001),   link_to_issue(issues(:issues_003), :subject => false)],
 1696       [attachments(:attachments_013),   '<a href="/boards/1/topics/1">First post</a>'],
 1697       [news_attachment,                 '<a href="/news/1">eCookbook first release !</a>'],
 1698       [attachments(:attachments_008),   '<a href="/projects/ecookbook/files">Files</a>'],
 1699       [attachments(:attachments_009),   '<a href="/projects/ecookbook/files">Files</a>'],
 1700       [attachments(:attachments_003),   '<a href="/projects/ecookbook/wiki/Page_with_an_inline_image">Page with an inline image</a>'],
 1701     ].each do |attachment, link|
 1702       assert_equal link, link_to_attachment_container(attachment.container)
 1703     end
 1704   end
 1705 
 1706   def test_principals_options_for_select_with_users
 1707     User.current = nil
 1708     users = [User.find(2), User.find(4)]
 1709     assert_equal(
 1710       %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
 1711       principals_options_for_select(users))
 1712   end
 1713 
 1714   def test_principals_options_for_select_with_selected
 1715     User.current = nil
 1716     users = [User.find(2), User.find(4)]
 1717     assert_equal(
 1718       %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
 1719       principals_options_for_select(users, User.find(4)))
 1720   end
 1721 
 1722   def test_principals_options_for_select_with_users_and_groups
 1723     User.current = nil
 1724     set_language_if_valid 'en'
 1725     users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
 1726     assert_equal(
 1727       %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
 1728       %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
 1729       principals_options_for_select(users))
 1730   end
 1731 
 1732   def test_principals_options_for_select_with_empty_collection
 1733     assert_equal '', principals_options_for_select([])
 1734   end
 1735 
 1736   def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
 1737     set_language_if_valid 'en'
 1738     users = [User.find(2), User.find(4)]
 1739     User.current = User.find(4)
 1740     assert_include '<option value="4">&lt;&lt; me &gt;&gt;</option>', principals_options_for_select(users)
 1741   end
 1742 
 1743   def test_stylesheet_link_tag_should_pick_the_default_stylesheet
 1744     assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
 1745   end
 1746 
 1747   def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
 1748     assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
 1749   end
 1750 
 1751   def test_image_tag_should_pick_the_default_image
 1752     assert_match 'src="/images/image.png"', image_tag("image.png")
 1753   end
 1754 
 1755   def test_image_tag_should_pick_the_theme_image_if_it_exists
 1756     theme = Redmine::Themes.themes.last
 1757     theme.images << 'image.png'
 1758 
 1759     with_settings :ui_theme => theme.id do
 1760       assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
 1761       assert_match %|src="/images/other.png"|, image_tag("other.png")
 1762     end
 1763   ensure
 1764     theme.images.delete 'image.png'
 1765   end
 1766 
 1767   def test_image_tag_sfor_plugin_should_pick_the_plugin_image
 1768     assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
 1769   end
 1770 
 1771   def test_javascript_include_tag_should_pick_the_default_javascript
 1772     assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
 1773   end
 1774 
 1775   def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
 1776     assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
 1777   end
 1778 
 1779   def test_raw_json_should_escape_closing_tags
 1780     s = raw_json(["<foo>bar</foo>"])
 1781     assert_include '\/foo', s
 1782   end
 1783 
 1784   def test_raw_json_should_be_html_safe
 1785     s = raw_json(["foo"])
 1786     assert s.html_safe?
 1787   end
 1788 
 1789   def test_html_title_should_app_title_if_not_set
 1790     assert_equal 'Redmine', html_title
 1791   end
 1792 
 1793   def test_html_title_should_join_items
 1794     html_title 'Foo', 'Bar'
 1795     assert_equal 'Foo - Bar - Redmine', html_title
 1796   end
 1797 
 1798   def test_html_title_should_append_current_project_name
 1799     @project = Project.find(1)
 1800     html_title 'Foo', 'Bar'
 1801     assert_equal 'Foo - Bar - eCookbook - Redmine', html_title
 1802   end
 1803 
 1804   def test_title_should_return_a_h2_tag
 1805     assert_equal '<h2>Foo</h2>', title('Foo')
 1806   end
 1807 
 1808   def test_title_should_set_html_title
 1809     title('Foo')
 1810     assert_equal 'Foo - Redmine', html_title
 1811   end
 1812 
 1813   def test_title_should_turn_arrays_into_links
 1814     assert_equal '<h2><a href="/foo">Foo</a></h2>', title(['Foo', '/foo'])
 1815     assert_equal 'Foo - Redmine', html_title
 1816   end
 1817 
 1818   def test_title_should_join_items
 1819     assert_equal '<h2>Foo &#187; Bar</h2>', title('Foo', 'Bar')
 1820     assert_equal 'Bar - Foo - Redmine', html_title
 1821   end
 1822 
 1823   def test_favicon_path
 1824     assert_match %r{^/favicon\.ico}, favicon_path
 1825   end
 1826 
 1827   def test_favicon_path_with_suburi
 1828     Redmine::Utils.relative_url_root = '/foo'
 1829     assert_match %r{^/foo/favicon\.ico}, favicon_path
 1830   ensure
 1831     Redmine::Utils.relative_url_root = ''
 1832   end
 1833 
 1834   def test_favicon_url
 1835     assert_match %r{^http://test\.host/favicon\.ico}, favicon_url
 1836   end
 1837 
 1838   def test_favicon_url_with_suburi
 1839     Redmine::Utils.relative_url_root = '/foo'
 1840     assert_match %r{^http://test\.host/foo/favicon\.ico}, favicon_url
 1841   ensure
 1842     Redmine::Utils.relative_url_root = ''
 1843   end
 1844 
 1845   def test_truncate_single_line
 1846     str = "01234"
 1847     result = truncate_single_line_raw("#{str}\n#{str}", 10)
 1848     assert_equal "01234 0...", result
 1849     assert !result.html_safe?
 1850     result = truncate_single_line_raw("#{str}<&#>\n#{str}#{str}", 16)
 1851     assert_equal "01234<&#> 012...", result
 1852     assert !result.html_safe?
 1853   end
 1854 
 1855   def test_truncate_single_line_non_ascii
 1856     ja = '日本語'
 1857     result = truncate_single_line_raw("#{ja}\n#{ja}\n#{ja}", 10)
 1858     assert_equal "#{ja} #{ja}...", result
 1859     assert !result.html_safe?
 1860   end
 1861 
 1862   def test_hours_formatting
 1863     set_language_if_valid 'en'
 1864 
 1865     with_settings :timespan_format => 'minutes' do
 1866       assert_equal '0:45', format_hours(0.75)
 1867       assert_equal '0:45 h', l_hours_short(0.75)
 1868       assert_equal '0:45 hour', l_hours(0.75)
 1869     end
 1870     with_settings :timespan_format => 'decimal' do
 1871       assert_equal '0.75', format_hours(0.75)
 1872       assert_equal '0.75 h', l_hours_short(0.75)
 1873       assert_equal '0.75 hour', l_hours(0.75)
 1874     end
 1875   end
 1876 
 1877   def test_html_hours
 1878     assert_equal '<span class="hours hours-int">0</span><span class="hours hours-dec">:45</span>', html_hours('0:45')
 1879     assert_equal '<span class="hours hours-int">0</span><span class="hours hours-dec">.75</span>', html_hours('0.75')
 1880   end
 1881 
 1882   def test_form_for_includes_name_attribute
 1883     assert_match(/name="new_issue-[a-z0-9]{8}"/, form_for(Issue.new){})
 1884   end
 1885 
 1886   def test_labelled_form_for_includes_name_attribute
 1887     assert_match(/name="new_issue-[a-z0-9]{8}"/, labelled_form_for(Issue.new){})
 1888   end
 1889 
 1890 
 1891   private
 1892 
 1893   def wiki_links_with_special_characters
 1894     return {
 1895       '[[Jack & Coke]]' =>
 1896           link_to("Jack & Coke",
 1897                   "/projects/ecookbook/wiki/Jack_&_Coke",
 1898                   :class => "wiki-page new"),
 1899       '[[a "quoted" name]]' =>
 1900           link_to("a \"quoted\" name",
 1901                   "/projects/ecookbook/wiki/A_%22quoted%22_name",
 1902                   :class => "wiki-page new"),
 1903       '[[le français, c\'est super]]' =>
 1904           link_to("le français, c\'est super",
 1905                   "/projects/ecookbook/wiki/Le_fran%C3%A7ais_c'est_super",
 1906                   :class => "wiki-page new"),
 1907       '[[broken < less]]' =>
 1908           link_to("broken < less",
 1909                   "/projects/ecookbook/wiki/Broken_%3C_less",
 1910                   :class => "wiki-page new"),
 1911       '[[broken > more]]' =>
 1912           link_to("broken > more",
 1913                   "/projects/ecookbook/wiki/Broken_%3E_more",
 1914                   :class => "wiki-page new"),
 1915       '[[[foo]Including [square brackets] in wiki title]]' =>
 1916           link_to("[foo]Including [square brackets] in wiki title",
 1917                   "/projects/ecookbook/wiki/%5Bfoo%5DIncluding_%5Bsquare_brackets%5D_in_wiki_title",
 1918                   :class => "wiki-page new"),
 1919     }
 1920   end
 1921 
 1922   def test_export_csv_encoding_select_tag_should_return_nil_when_general_csv_encoding_is_UTF8
 1923     with_locale 'az' do
 1924       assert_equal l(:general_csv_encoding), 'UTF-8'
 1925       assert_nil export_csv_encoding_select_tag
 1926     end
 1927   end
 1928 
 1929   def test_export_csv_encoding_select_tag_should_have_two_option_when_general_csv_encoding_is_not_UTF8
 1930     with_locale 'en' do
 1931       assert_not_equal l(:general_csv_encoding), 'UTF-8'
 1932       result = export_csv_encoding_select_tag
 1933       assert_select_in result, "option[selected='selected'][value=#{l(:general_csv_encoding)}]", :text => l(:general_csv_encoding)
 1934       assert_select_in result, "option[value='UTF-8']", :text => 'UTF-8'
 1935     end
 1936   end
 1937 end