From a9e4b41d4afba7eec48d2a0f344fd768d3d84175 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sun, 30 Jul 2017 18:23:06 +0000 Subject: Move helper tests to test/helpers (#26504). git-svn-id: http://svn.redmine.org/redmine/trunk@16930 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- test/helpers/activities_helper_test.rb | 102 ++ test/helpers/application_helper_test.rb | 1582 +++++++++++++++++++++++++++++ test/helpers/custom_fields_helper_test.rb | 89 ++ test/helpers/empty | 0 test/helpers/groups_helper_test.rb | 42 + test/helpers/issues_helper_test.rb | 332 ++++++ test/helpers/journals_helper_test.rb | 48 + test/helpers/members_helper_test.rb | 43 + test/helpers/projects_helper_test.rb | 78 ++ test/helpers/queries_helper_test.rb | 96 ++ test/helpers/routes_helper_test.rb | 43 + test/helpers/search_helper_test.rb | 48 + test/helpers/settings_helper_test.rb | 30 + test/helpers/sort_helper_test.rb | 109 ++ test/helpers/timelog_helper_test.rb | 53 + test/helpers/version_helper_test.rb | 54 + test/helpers/watchers_helper_test.rb | 67 ++ test/helpers/wiki_helper_test.rb | 45 + 18 files changed, 2861 insertions(+) create mode 100644 test/helpers/activities_helper_test.rb create mode 100644 test/helpers/application_helper_test.rb create mode 100644 test/helpers/custom_fields_helper_test.rb delete mode 100644 test/helpers/empty create mode 100644 test/helpers/groups_helper_test.rb create mode 100644 test/helpers/issues_helper_test.rb create mode 100644 test/helpers/journals_helper_test.rb create mode 100644 test/helpers/members_helper_test.rb create mode 100644 test/helpers/projects_helper_test.rb create mode 100644 test/helpers/queries_helper_test.rb create mode 100644 test/helpers/routes_helper_test.rb create mode 100644 test/helpers/search_helper_test.rb create mode 100644 test/helpers/settings_helper_test.rb create mode 100644 test/helpers/sort_helper_test.rb create mode 100644 test/helpers/timelog_helper_test.rb create mode 100644 test/helpers/version_helper_test.rb create mode 100644 test/helpers/watchers_helper_test.rb create mode 100644 test/helpers/wiki_helper_test.rb (limited to 'test/helpers') diff --git a/test/helpers/activities_helper_test.rb b/test/helpers/activities_helper_test.rb new file mode 100644 index 000000000..b4a03369a --- /dev/null +++ b/test/helpers/activities_helper_test.rb @@ -0,0 +1,102 @@ +# Redmine - project management software +# Copyright (C) 2006-2017 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) + +class ActivitiesHelperTest < Redmine::HelperTest + include ActivitiesHelper + + class MockEvent + attr_reader :event_datetime, :event_group, :name + + def initialize(group=nil) + @@count ||= 0 + @name = "e#{@@count}" + @event_datetime = Time.now + @@count.hours + @event_group = group || self + @@count += 1 + end + + def self.clear + @@count = 0 + end + end + + def setup + super + MockEvent.clear + end + + def test_sort_activity_events_should_sort_by_datetime + events = [] + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new + + assert_equal [ + ['e2', false], + ['e1', false], + ['e0', false] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_should_group_events + events = [] + events << MockEvent.new + events << MockEvent.new(events[0]) + events << MockEvent.new(events[0]) + + assert_equal [ + ['e2', false], + ['e1', true], + ['e0', true] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_with_group_not_in_set_should_group_events + e = MockEvent.new + events = [] + events << MockEvent.new(e) + events << MockEvent.new(e) + + assert_equal [ + ['e2', false], + ['e1', true] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_should_sort_by_datetime_and_group + events = [] + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new(events[1]) + events << MockEvent.new(events[2]) + events << MockEvent.new + events << MockEvent.new(events[2]) + + assert_equal [ + ['e6', false], + ['e4', true], + ['e2', true], + ['e5', false], + ['e3', false], + ['e1', true], + ['e0', false] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end +end diff --git a/test/helpers/application_helper_test.rb b/test/helpers/application_helper_test.rb new file mode 100644 index 000000000..6a461c31a --- /dev/null +++ b/test/helpers/application_helper_test.rb @@ -0,0 +1,1582 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2017 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) + +class ApplicationHelperTest < Redmine::HelperTest + include ERB::Util + include Rails.application.routes.url_helpers + + fixtures :projects, :enabled_modules, + :users, :email_addresses, + :members, :member_roles, :roles, + :repositories, :changesets, + :projects_trackers, + :trackers, :issue_statuses, :issues, :versions, :documents, + :wikis, :wiki_pages, :wiki_contents, + :boards, :messages, :news, + :attachments, :enumerations + + def setup + super + set_tmp_attachments_directory + @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82".force_encoding('UTF-8') + end + + test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do + User.current = User.find_by_login('admin') + + @project = Issue.first.project # Used by helper + response = link_to_if_authorized('By controller/actionr', + {:controller => 'issues', :action => 'edit', :id => Issue.first.id}) + assert_match /href/, response + end + + test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do + User.current = User.find_by_login('dlopper') + @project = Project.find('private-child') + issue = @project.issues.first + assert !issue.visible? + + response = link_to_if_authorized('Never displayed', + {:controller => 'issues', :action => 'show', :id => issue}) + assert_nil response + end + + def test_auto_links + to_test = { + 'http://foo.bar' => 'http://foo.bar', + 'http://foo.bar/~user' => 'http://foo.bar/~user', + 'http://foo.bar.' => 'http://foo.bar.', + 'https://foo.bar.' => 'https://foo.bar.', + 'This is a link: http://foo.bar.' => 'This is a link: http://foo.bar.', + 'A link (eg. http://foo.bar).' => 'A link (eg. http://foo.bar).', + 'http://foo.bar/foo.bar#foo.bar.' => 'http://foo.bar/foo.bar#foo.bar.', + 'http://www.foo.bar/Test_(foobar)' => 'http://www.foo.bar/Test_(foobar)', + '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : http://www.foo.bar/Test_(foobar))', + '(see inline link : http://www.foo.bar/Test)' => '(see inline link : http://www.foo.bar/Test)', + '(see inline link : http://www.foo.bar/Test).' => '(see inline link : http://www.foo.bar/Test).', + '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see inline link)', + '(see "inline link":http://www.foo.bar/Test)' => '(see inline link)', + '(see "inline link":http://www.foo.bar/Test).' => '(see inline link).', + 'www.foo.bar' => 'www.foo.bar', + 'http://foo.bar/page?p=1&t=z&s=' => 'http://foo.bar/page?p=1&t=z&s=', + 'http://foo.bar/page#125' => 'http://foo.bar/page#125', + 'http://foo@www.bar.com' => 'http://foo@www.bar.com', + 'http://foo:bar@www.bar.com' => 'http://foo:bar@www.bar.com', + 'ftp://foo.bar' => 'ftp://foo.bar', + 'ftps://foo.bar' => 'ftps://foo.bar', + 'sftp://foo.bar' => 'sftp://foo.bar', + # two exclamation marks + 'http://example.net/path!602815048C7B5C20!302.html' => 'http://example.net/path!602815048C7B5C20!302.html', + # escaping + 'http://foo"bar' => 'http://foo"bar', + # wrap in angle brackets + '' => '<http://foo.bar>', + # invalid urls + 'http://' => 'http://', + 'www.' => 'www.', + 'test-www.bar.com' => 'test-www.bar.com', + } + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text) } + end + + def test_auto_links_with_non_ascii_characters + to_test = { + "http://foo.bar/#{@russian_test}" => + %|http://foo.bar/#{@russian_test}| + } + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text) } + end + + def test_auto_mailto + to_test = { + 'test@foo.bar' => '', + 'test@www.foo.bar' => '', + } + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text) } + end + + def test_inline_images + to_test = { + '!http://foo.bar/image.jpg!' => '', + 'floating !>http://foo.bar/image.jpg!' => 'floating ', + 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class ', + 'with class !(wiki-class-foo)http://foo.bar/image.jpg!' => 'with class ', + 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style ', + 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title This is a title', + 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title This is a double-quoted "title"', + } + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text) } + end + + def test_inline_images_inside_tags + raw = <<-RAW +h1. !foo.png! Heading + +Centered image: + +p=. !bar.gif! +RAW + + assert textilizable(raw).include?('') + assert textilizable(raw).include?('') + end + + def test_attached_images + to_test = { + 'Inline image: !logo.gif!' => 'Inline image: This is a logo', + 'Inline image: !logo.GIF!' => 'Inline image: This is a logo', + 'No match: !ogo.gif!' => 'No match: ', + 'No match: !ogo.GIF!' => 'No match: ', + # link image + '!logo.gif!:http://foo.bar/' => 'This is a logo', + } + attachments = Attachment.all + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text, :attachments => attachments) } + end + + def test_attached_images_with_textile_and_non_ascii_filename + attachment = Attachment.generate!(:filename => 'café.jpg') + with_settings :text_formatting => 'textile' do + assert_include %(), + textilizable("!café.jpg!)", :attachments => [attachment]) + end + end + + def test_attached_images_with_markdown_and_non_ascii_filename + skip unless Object.const_defined?(:Redcarpet) + + attachment = Attachment.generate!(:filename => 'café.jpg') + with_settings :text_formatting => 'markdown' do + assert_include %(), + textilizable("![](café.jpg)", :attachments => [attachment]) + end + end + + def test_attached_images_with_hires_naming + attachment = Attachment.generate!(:filename => 'image@2x.png') + assert_equal %(

), + textilizable("!image@2x.png!", :attachments => [attachment]) + end + + def test_attached_images_filename_extension + set_tmp_attachments_directory + a1 = Attachment.new( + :container => Issue.find(1), + :file => mock_file_with_options({:original_filename => "testtest.JPG"}), + :author => User.find(1)) + assert a1.save + assert_equal "testtest.JPG", a1.filename + assert_equal "image/jpeg", a1.content_type + assert a1.image? + + a2 = Attachment.new( + :container => Issue.find(1), + :file => mock_file_with_options({:original_filename => "testtest.jpeg"}), + :author => User.find(1)) + assert a2.save + assert_equal "testtest.jpeg", a2.filename + assert_equal "image/jpeg", a2.content_type + assert a2.image? + + a3 = Attachment.new( + :container => Issue.find(1), + :file => mock_file_with_options({:original_filename => "testtest.JPE"}), + :author => User.find(1)) + assert a3.save + assert_equal "testtest.JPE", a3.filename + assert_equal "image/jpeg", a3.content_type + assert a3.image? + + a4 = Attachment.new( + :container => Issue.find(1), + :file => mock_file_with_options({:original_filename => "Testtest.BMP"}), + :author => User.find(1)) + assert a4.save + assert_equal "Testtest.BMP", a4.filename + assert_equal "image/x-ms-bmp", a4.content_type + assert a4.image? + + to_test = { + 'Inline image: !testtest.jpg!' => + 'Inline image: ', + 'Inline image: !testtest.jpeg!' => + 'Inline image: ', + 'Inline image: !testtest.jpe!' => + 'Inline image: ', + 'Inline image: !testtest.bmp!' => + 'Inline image: ', + } + + attachments = [a1, a2, a3, a4] + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text, :attachments => attachments) } + end + + def test_attached_images_should_read_later + set_fixtures_attachments_directory + a1 = Attachment.find(16) + assert_equal "testfile.png", a1.filename + assert a1.readable? + assert (! a1.visible?(User.anonymous)) + assert a1.visible?(User.find(2)) + a2 = Attachment.find(17) + assert_equal "testfile.PNG", a2.filename + assert a2.readable? + assert (! a2.visible?(User.anonymous)) + assert a2.visible?(User.find(2)) + assert a1.created_on < a2.created_on + + to_test = { + 'Inline image: !testfile.png!' => + 'Inline image: ', + 'Inline image: !Testfile.PNG!' => + 'Inline image: ', + } + attachments = [a1, a2] + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text, :attachments => attachments) } + set_tmp_attachments_directory + end + + def test_textile_external_links + to_test = { + 'This is a "link":http://foo.bar' => 'This is a link', + 'This is an intern "link":/foo/bar' => 'This is an intern link', + '"link (Link title)":http://foo.bar' => 'link', + '"link (Link title with "double-quotes")":http://foo.bar' => 'link', + "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":

\n\n\n\t

Another paragraph", + # no multiline link text + "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
and another on a second line\":test", + # mailto link + "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "system administrator", + # two exclamation marks + '"a link":http://example.net/path!602815048C7B5C20!302.html' => 'a link', + # escaping + '"test":http://foo"bar' => 'test', + } + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text) } + end + + def test_textile_external_links_with_non_ascii_characters + to_test = { + %|This is a "link":http://foo.bar/#{@russian_test}| => + %|This is a link| + } + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text) } + end + + def test_redmine_links + issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3}, + :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)') + note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'}, + :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)') + note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'}, + :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)') + + revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1}, + :class => 'changeset', :title => 'My very first commit do not escaping #<>&') + revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, + :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3') + + changeset_link2 = link_to('691322a8eb01e11fd7', + {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1}, + :class => 'changeset', :title => 'My very first commit do not escaping #<>&') + + document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1}, + :class => 'document') + + version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2}, + :class => 'version') + + board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'} + + message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4} + + news_url = {:controller => 'news', :action => 'show', :id => 1} + + project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'} + + source_url = '/projects/ecookbook/repository/entry/some/file' + source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file' + source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext' + source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext' + source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file' + + export_url = '/projects/ecookbook/repository/raw/some/file' + export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file' + export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext' + export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext' + export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file' + + to_test = { + # tickets + '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.", + # ticket notes + '#3-14' => note_link, + '#3#note-14' => note_link2, + # should not ignore leading zero + '#03' => '#03', + # changesets + 'r1' => revision_link, + 'r1.' => "#{revision_link}.", + 'r1, r2' => "#{revision_link}, #{revision_link2}", + 'r1,r2' => "#{revision_link},#{revision_link2}", + 'commit:691322a8eb01e11fd7' => changeset_link2, + # documents + 'document#1' => document_link, + 'document:"Test document"' => document_link, + # versions + 'version#2' => version_link, + 'version:1.0' => version_link, + 'version:"1.0"' => version_link, + # source + 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'), + 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'), + 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".", + 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".", + 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".", + 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".", + 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",", + 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'), + 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'), + 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'), + 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'), + 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'), + 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'), + # export + 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'), + 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'), + 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'), + 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'), + 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'), + # forum + 'forum#2' => link_to('Discussion', board_url, :class => 'board'), + 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'), + # message + 'message#4' => link_to('Post 2', message_url, :class => 'message'), + 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'), + # news + 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'), + 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'), + # project + 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'), + 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'), + 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'), + # not found + '#0123456789' => '#0123456789', + # invalid expressions + 'source:' => 'source:', + # url hash + "http://foo.bar/FAQ#3" => 'http://foo.bar/FAQ#3', + # user + 'user:jsmith' => link_to_user(User.find_by_id(2)), + 'user#2' => link_to_user(User.find_by_id(2)), + '@jsmith' => link_to_user(User.find_by_id(2)), + # invalid user + 'user:foobar' => 'user:foobar', + } + @project = Project.find(1) + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text), "#{text} failed" } + end + + def test_user_links_with_email_as_login_name_should_not_be_parsed + u = User.generate!(:login => 'jsmith@somenet.foo') + raw = "@jsmith@somenet.foo should not be parsed in jsmith@somenet.foo" + + assert_match %r{

#{u.name} should not be parsed in

}, + textilizable(raw, :project => Project.find(1)) + end + + def test_should_not_parse_redmine_links_inside_link + raw = "r1 should not be parsed in http://example.com/url-r1/" + assert_match %r{

r1 should not be parsed in http://example.com/url-r1/

}, + textilizable(raw, :project => Project.find(1)) + end + + def test_redmine_links_with_a_different_project_before_current_project + vp1 = Version.generate!(:project_id => 1, :name => '1.4.4') + vp3 = Version.generate!(:project_id => 3, :name => '1.4.4') + @project = Project.find(3) + result1 = link_to("1.4.4", "/versions/#{vp1.id}", :class => "version") + result2 = link_to("1.4.4", "/versions/#{vp3.id}", :class => "version") + assert_equal "

#{result1} #{result2}

", + textilizable("ecookbook:version:1.4.4 version:1.4.4") + end + + def test_escaped_redmine_links_should_not_be_parsed + to_test = [ + '#3.', + '#3-14.', + '#3#-note14.', + 'r1', + 'document#1', + 'document:"Test document"', + 'version#2', + 'version:1.0', + 'version:"1.0"', + 'source:/some/file' + ] + @project = Project.find(1) + to_test.each { |text| assert_equal "

#{text}

", textilizable("!" + text), "#{text} failed" } + end + + def test_cross_project_redmine_links + source_link = link_to('ecookbook:source:/some/file', + {:controller => 'repositories', :action => 'entry', + :id => 'ecookbook', :path => ['some', 'file']}, + :class => 'source') + changeset_link = link_to('ecookbook:r2', + {:controller => 'repositories', :action => 'revision', + :id => 'ecookbook', :rev => 2}, + :class => 'changeset', + :title => 'This commit fixes #1, #2 and references #1 & #3') + to_test = { + # documents + 'document:"Test document"' => 'document:"Test document"', + 'ecookbook:document:"Test document"' => + link_to("Test document", "/documents/1", :class => "document"), + 'invalid:document:"Test document"' => 'invalid:document:"Test document"', + # versions + 'version:"1.0"' => 'version:"1.0"', + 'ecookbook:version:"1.0"' => + link_to("1.0", "/versions/2", :class => "version"), + 'invalid:version:"1.0"' => 'invalid:version:"1.0"', + # changeset + 'r2' => 'r2', + 'ecookbook:r2' => changeset_link, + 'invalid:r2' => 'invalid:r2', + # source + 'source:/some/file' => 'source:/some/file', + 'ecookbook:source:/some/file' => source_link, + 'invalid:source:/some/file' => 'invalid:source:/some/file', + } + @project = Project.find(3) + to_test.each do |text, result| + assert_equal "

#{result}

", textilizable(text), "#{text} failed" + end + end + + def test_redmine_links_by_name_should_work_with_html_escaped_characters + v = Version.generate!(:name => "Test & Show.txt", :project_id => 1) + link = link_to("Test & Show.txt", "/versions/#{v.id}", :class => "version") + + @project = v.project + assert_equal "

#{link}

", textilizable('version:"Test & Show.txt"') + end + + def test_link_to_issue_subject + issue = Issue.generate!(:subject => "01234567890123456789") + str = link_to_issue(issue, :truncate => 10) + result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes) + assert_equal "#{result}: 0123456...", str + + issue = Issue.generate!(:subject => "<&>") + str = link_to_issue(issue) + result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes) + assert_equal "#{result}: <&>", str + + issue = Issue.generate!(:subject => "<&>0123456789012345") + str = link_to_issue(issue, :truncate => 10) + result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes) + assert_equal "#{result}: <&>0123...", str + end + + def test_link_to_issue_title + long_str = "0123456789" * 5 + + issue = Issue.generate!(:subject => "#{long_str}01234567890123456789") + str = link_to_issue(issue, :subject => false) + result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", + :class => issue.css_classes, + :title => "#{long_str}0123456...") + assert_equal result, str + + issue = Issue.generate!(:subject => "<&>#{long_str}01234567890123456789") + str = link_to_issue(issue, :subject => false) + result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", + :class => issue.css_classes, + :title => "<&>#{long_str}0123...") + assert_equal result, str + end + + def test_multiple_repositories_redmine_links + svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg') + Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123') + hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg') + Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd') + + changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, + :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3') + svn_changeset_link = link_to('svn_repo-1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123}, + :class => 'changeset', :title => '') + hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'}, + :class => 'changeset', :title => '') + + source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source') + hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source') + + to_test = { + 'r2' => changeset_link, + 'svn_repo-1|r123' => svn_changeset_link, + 'invalid|r123' => 'invalid|r123', + 'commit:hg1|abcd' => hg_changeset_link, + 'commit:invalid|abcd' => 'commit:invalid|abcd', + # source + 'source:some/file' => source_link, + 'source:hg1|some/file' => hg_source_link, + 'source:invalid|some/file' => 'source:invalid|some/file', + } + + @project = Project.find(1) + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text), "#{text} failed" } + end + + def test_cross_project_multiple_repositories_redmine_links + svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg') + Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123') + hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg') + Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd') + + changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, + :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3') + svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123}, + :class => 'changeset', :title => '') + hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'}, + :class => 'changeset', :title => '') + + source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source') + hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source') + + to_test = { + 'ecookbook:r2' => changeset_link, + 'ecookbook:svn1|r123' => svn_changeset_link, + 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123', + 'ecookbook:commit:hg1|abcd' => hg_changeset_link, + 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd', + 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd', + # source + 'ecookbook:source:some/file' => source_link, + 'ecookbook:source:hg1|some/file' => hg_source_link, + 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file', + 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file', + } + + @project = Project.find(3) + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text), "#{text} failed" } + end + + def test_redmine_links_git_commit + changeset_link = link_to('abcd', + { + :controller => 'repositories', + :action => 'revision', + :id => 'subproject1', + :rev => 'abcd', + }, + :class => 'changeset', :title => 'test commit') + to_test = { + 'commit:abcd' => changeset_link, + } + @project = Project.find(3) + r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git') + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => 'abcd', + :scmid => 'abcd', + :comments => 'test commit') + assert( c.save ) + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text) } + end + + # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'. + def test_redmine_links_mercurial_commit + changeset_link_rev = link_to('r123', + { + :controller => 'repositories', + :action => 'revision', + :id => 'subproject1', + :rev => '123' , + }, + :class => 'changeset', :title => 'test commit') + changeset_link_commit = link_to('abcd', + { + :controller => 'repositories', + :action => 'revision', + :id => 'subproject1', + :rev => 'abcd' , + }, + :class => 'changeset', :title => 'test commit') + to_test = { + 'r123' => changeset_link_rev, + 'commit:abcd' => changeset_link_commit, + } + @project = Project.find(3) + r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test') + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => 'abcd', + :comments => 'test commit') + assert( c.save ) + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text) } + end + + def test_attachment_links + text = 'attachment:error281.txt' + result = link_to("error281.txt", "/attachments/1/error281.txt", + :class => "attachment") + assert_equal "

#{result}

", + textilizable(text, + :attachments => Issue.find(3).attachments), + "#{text} failed" + end + + def test_attachment_link_should_link_to_latest_attachment + set_tmp_attachments_directory + a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago) + a2 = Attachment.generate!(:filename => "test.txt") + result = link_to("test.txt", "/attachments/#{a2.id}/test.txt", + :class => "attachment") + assert_equal "

#{result}

", + textilizable('attachment:test.txt', :attachments => [a1, a2]) + end + + def test_wiki_links + User.current = User.find_by_login('jsmith') + russian_eacape = CGI.escape(@russian_test) + to_test = { + '[[CookBook documentation]]' => + link_to("CookBook documentation", + "/projects/ecookbook/wiki/CookBook_documentation", + :class => "wiki-page"), + '[[Another page|Page]]' => + link_to("Page", + "/projects/ecookbook/wiki/Another_page", + :class => "wiki-page"), + # title content should be formatted + '[[Another page|With _styled_ *title*]]' => + link_to("With styled title".html_safe, + "/projects/ecookbook/wiki/Another_page", + :class => "wiki-page"), + '[[Another page|With title containing HTML entities & markups]]' => + link_to("With title containing <strong>HTML entities & markups</strong>".html_safe, + "/projects/ecookbook/wiki/Another_page", + :class => "wiki-page"), + # link with anchor + '[[CookBook documentation#One-section]]' => + link_to("CookBook documentation", + "/projects/ecookbook/wiki/CookBook_documentation#One-section", + :class => "wiki-page"), + '[[Another page#anchor|Page]]' => + link_to("Page", + "/projects/ecookbook/wiki/Another_page#anchor", + :class => "wiki-page"), + # UTF8 anchor + "[[Another_page##{@russian_test}|#{@russian_test}]]" => + link_to(@russian_test, + "/projects/ecookbook/wiki/Another_page##{russian_eacape}", + :class => "wiki-page"), + # page that doesn't exist + '[[Unknown page]]' => + link_to("Unknown page", + "/projects/ecookbook/wiki/Unknown_page", + :class => "wiki-page new"), + '[[Unknown page|404]]' => + link_to("404", + "/projects/ecookbook/wiki/Unknown_page", + :class => "wiki-page new"), + # link to another project wiki + '[[onlinestore:]]' => + link_to("onlinestore", + "/projects/onlinestore/wiki", + :class => "wiki-page"), + '[[onlinestore:|Wiki]]' => + link_to("Wiki", + "/projects/onlinestore/wiki", + :class => "wiki-page"), + '[[onlinestore:Start page]]' => + link_to("Start page", + "/projects/onlinestore/wiki/Start_page", + :class => "wiki-page"), + '[[onlinestore:Start page|Text]]' => + link_to("Text", + "/projects/onlinestore/wiki/Start_page", + :class => "wiki-page"), + '[[onlinestore:Unknown page]]' => + link_to("Unknown page", + "/projects/onlinestore/wiki/Unknown_page", + :class => "wiki-page new"), + # struck through link + '-[[Another page|Page]]-' => + "".html_safe + + link_to("Page", + "/projects/ecookbook/wiki/Another_page", + :class => "wiki-page").html_safe + + "".html_safe, + '-[[Another page|Page]] link-' => + "".html_safe + + link_to("Page", + "/projects/ecookbook/wiki/Another_page", + :class => "wiki-page").html_safe + + " link".html_safe, + # escaping + '![[Another page|Page]]' => '[[Another page|Page]]', + # project does not exist + '[[unknowproject:Start]]' => '[[unknowproject:Start]]', + '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]', + # missing permission to view wiki in project + '[[private-child:]]' => '[[private-child:]]', + '[[private-child:Wiki]]' => '[[private-child:Wiki]]', + } + @project = Project.find(1) + to_test.each { |text, result| assert_equal "

#{result}

", textilizable(text) } + end + + def test_wiki_links_within_local_file_generation_context + to_test = { + # link to a page + '[[CookBook documentation]]' => + link_to("CookBook documentation", "CookBook_documentation.html", + :class => "wiki-page"), + '[[CookBook documentation|documentation]]' => + link_to("documentation", "CookBook_documentation.html", + :class => "wiki-page"), + '[[CookBook documentation#One-section]]' => + link_to("CookBook documentation", "CookBook_documentation.html#One-section", + :class => "wiki-page"), + '[[CookBook documentation#One-section|documentation]]' => + link_to("documentation", "CookBook_documentation.html#One-section", + :class => "wiki-page"), + # page that doesn't exist + '[[Unknown page]]' => + link_to("Unknown page", "Unknown_page.html", + :class => "wiki-page new"), + '[[Unknown page|404]]' => + link_to("404", "Unknown_page.html", + :class => "wiki-page new"), + '[[Unknown page#anchor]]' => + link_to("Unknown page", "Unknown_page.html#anchor", + :class => "wiki-page new"), + '[[Unknown page#anchor|404]]' => + link_to("404", "Unknown_page.html#anchor", + :class => "wiki-page new"), + } + @project = Project.find(1) + to_test.each do |text, result| + assert_equal "

#{result}

", textilizable(text, :wiki_links => :local) + end + end + + def test_wiki_links_within_wiki_page_context + page = WikiPage.find_by_title('Another_page' ) + to_test = { + '[[CookBook documentation]]' => + link_to("CookBook documentation", + "/projects/ecookbook/wiki/CookBook_documentation", + :class => "wiki-page"), + '[[CookBook documentation|documentation]]' => + link_to("documentation", + "/projects/ecookbook/wiki/CookBook_documentation", + :class => "wiki-page"), + '[[CookBook documentation#One-section]]' => + link_to("CookBook documentation", + "/projects/ecookbook/wiki/CookBook_documentation#One-section", + :class => "wiki-page"), + '[[CookBook documentation#One-section|documentation]]' => + link_to("documentation", + "/projects/ecookbook/wiki/CookBook_documentation#One-section", + :class => "wiki-page"), + # link to the current page + '[[Another page]]' => + link_to("Another page", + "/projects/ecookbook/wiki/Another_page", + :class => "wiki-page"), + '[[Another page|Page]]' => + link_to("Page", + "/projects/ecookbook/wiki/Another_page", + :class => "wiki-page"), + '[[Another page#anchor]]' => + link_to("Another page", + "#anchor", + :class => "wiki-page"), + '[[Another page#anchor|Page]]' => + link_to("Page", + "#anchor", + :class => "wiki-page"), + # page that doesn't exist + '[[Unknown page]]' => + link_to("Unknown page", + "/projects/ecookbook/wiki/Unknown_page?parent=Another_page", + :class => "wiki-page new"), + '[[Unknown page|404]]' => + link_to("404", + "/projects/ecookbook/wiki/Unknown_page?parent=Another_page", + :class => "wiki-page new"), + '[[Unknown page#anchor]]' => + link_to("Unknown page", + "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor", + :class => "wiki-page new"), + '[[Unknown page#anchor|404]]' => + link_to("404", + "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor", + :class => "wiki-page new"), + } + @project = Project.find(1) + to_test.each do |text, result| + assert_equal "

#{result}

", + textilizable(WikiContent.new( :text => text, :page => page ), :text) + end + end + + def test_wiki_links_anchor_option_should_prepend_page_title_to_href + to_test = { + # link to a page + '[[CookBook documentation]]' => + link_to("CookBook documentation", + "#CookBook_documentation", + :class => "wiki-page"), + '[[CookBook documentation|documentation]]' => + link_to("documentation", + "#CookBook_documentation", + :class => "wiki-page"), + '[[CookBook documentation#One-section]]' => + link_to("CookBook documentation", + "#CookBook_documentation_One-section", + :class => "wiki-page"), + '[[CookBook documentation#One-section|documentation]]' => + link_to("documentation", + "#CookBook_documentation_One-section", + :class => "wiki-page"), + # page that doesn't exist + '[[Unknown page]]' => + link_to("Unknown page", + "#Unknown_page", + :class => "wiki-page new"), + '[[Unknown page|404]]' => + link_to("404", + "#Unknown_page", + :class => "wiki-page new"), + '[[Unknown page#anchor]]' => + link_to("Unknown page", + "#Unknown_page_anchor", + :class => "wiki-page new"), + '[[Unknown page#anchor|404]]' => + link_to("404", + "#Unknown_page_anchor", + :class => "wiki-page new"), + } + @project = Project.find(1) + to_test.each do |text, result| + assert_equal "

#{result}

", textilizable(text, :wiki_links => :anchor) + end + end + + def test_html_tags + to_test = { + "
content
" => "

<div>content</div>

", + "
content
" => "

<div class=\"bold\">content</div>

", + "" => "

<script>some script;</script>

", + # do not escape pre/code tags + "
\nline 1\nline2
" => "
\nline 1\nline2
", + "
\nline 1\nline2
" => "
\nline 1\nline2
", + "
content
" => "
<div>content</div>
", + "HTML comment: " => "

HTML comment: <!-- no comments -->

", + "