diff options
Diffstat (limited to 'test')
71 files changed, 2269 insertions, 155 deletions
diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index 4a6fd0d30..38d69e7c8 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -43,6 +43,11 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driver_option.add_preference 'download.default_directory', DOWNLOADS_PATH.gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR) driver_option.add_preference 'download.prompt_for_download', false driver_option.add_preference 'plugins.plugins_disabled', ["Chrome PDF Viewer"] + # Disable "Change your password" popup shown after login due to leak detection + driver_option.add_preference 'profile.password_manager_leak_detection', false + # Disable password saving prompts + driver_option.add_preference 'profile.password_manager_enabled', false + driver_option.add_preference 'credentials_enable_service', false end setup do @@ -68,13 +73,13 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase # using default browser locale which depend on system locale for "real" browsers drivers def log_user(login, password) visit '/my/page' - assert_equal '/login', current_path + assert_current_path '/login', :ignore_query => true within('#login-form form') do fill_in 'username', :with => login fill_in 'password', :with => password find('input[name=login]').click end - assert_equal '/my/page', current_path + assert_current_path '/my/page', :ignore_query => true end def wait_for_ajax diff --git a/test/fixtures/changesets.yml b/test/fixtures/changesets.yml index 247dda375..8eaca6788 100644 --- a/test/fixtures/changesets.yml +++ b/test/fixtures/changesets.yml @@ -102,3 +102,15 @@ changesets_010: user_id: 3 repository_id: 10 committer: dlopper +changesets_011: + commit_date: "2025-04-07" + comments: |- + This commit references an issue and a [[wiki]] page + Refs #2 + committed_on: 2025-04-07 19:00:00 + revision: "11" + id: 110 + scmid: + user_id: 3 + repository_id: 10 + committer: dlopper diff --git a/test/fixtures/reactions.yml b/test/fixtures/reactions.yml new file mode 100644 index 000000000..d8fcbfc1b --- /dev/null +++ b/test/fixtures/reactions.yml @@ -0,0 +1,51 @@ +--- +reaction_001: + id: 1 + reactable_type: Issue + reactable_id: 1 + user_id: 1 +reaction_002: + id: 2 + reactable_type: Issue + reactable_id: 1 + user_id: 2 +reaction_003: + id: 3 + reactable_type: Issue + reactable_id: 1 + user_id: 3 +reaction_004: + id: 4 + reactable_type: Journal + reactable_id: 1 + user_id: 2 +reaction_005: + id: 5 + reactable_type: Issue + reactable_id: 6 + user_id: 2 +reaction_006: + id: 6 + reactable_type: Journal + reactable_id: 4 + user_id: 2 +reaction_007: + id: 7 + reactable_type: News + reactable_id: 1 + user_id: 1 +reaction_008: + id: 8 + reactable_type: Comment + reactable_id: 1 + user_id: 2 +reaction_009: + id: 9 + reactable_type: Message + reactable_id: 7 + user_id: 2 +reaction_010: + id: 10 + reactable_type: News + reactable_id: 3 + user_id: 2 diff --git a/test/functional/attachments_controller_test.rb b/test/functional/attachments_controller_test.rb index 04fdb15d2..c2e7e2f7b 100644 --- a/test/functional/attachments_controller_test.rb +++ b/test/functional/attachments_controller_test.rb @@ -42,7 +42,7 @@ class AttachmentsControllerTest < Redmine::ControllerTest assert_response :success assert_equal 'text/html', @response.media_type - assert_select 'th.filename', :text => /issues_controller.rb\t\(révision 1484\)/ + assert_select 'th.filename', :text => /issues_controller\.rb \(révision 1484\)/ assert_select 'td.line-code', :text => /Demande créée avec succès/ end end @@ -61,7 +61,7 @@ class AttachmentsControllerTest < Redmine::ControllerTest assert_response :success assert_equal 'text/html', @response.media_type - assert_select 'th.filename', :text => /issues_controller.rb\t\(r\?vision 1484\)/ + assert_select 'th.filename', :text => /issues_controller\.rb \(r\?vision 1484\)/ assert_select 'td.line-code', :text => /Demande cr\?\?e avec succ\?s/ end end @@ -81,7 +81,7 @@ class AttachmentsControllerTest < Redmine::ControllerTest assert_response :success assert_equal 'text/html', @response.media_type - assert_select 'th.filename', :text => /issues_controller.rb\t\(révision 1484\)/ + assert_select 'th.filename', :text => /issues_controller\.rb \(révision 1484\)/ assert_select 'td.line-code', :text => /Demande créée avec succès/ end end diff --git a/test/functional/calendars_controller_test.rb b/test/functional/calendars_controller_test.rb index 227919435..202c076ac 100644 --- a/test/functional/calendars_controller_test.rb +++ b/test/functional/calendars_controller_test.rb @@ -57,7 +57,7 @@ class CalendarsControllerTest < Redmine::ControllerTest ) do assert_select 'a.issue[href=?]', '/issues/2', :text => 'Feature request #2' assert_select 'span.tip' do - assert_select 'img[class="gravatar"]' + assert_select 'img[class="gravatar avatar"]' end assert_select 'input[name=?][type=?][value=?]', 'ids[]', 'checkbox', '2' end diff --git a/test/functional/custom_fields_controller_test.rb b/test/functional/custom_fields_controller_test.rb index 0706a2eda..fdd2a4148 100644 --- a/test/functional/custom_fields_controller_test.rb +++ b/test/functional/custom_fields_controller_test.rb @@ -282,6 +282,23 @@ class CustomFieldsControllerTest < Redmine::ControllerTest assert_select '[name=?]', 'custom_field[full_width_layout]', 0 end + def test_setting_ratio_interval_should_be_present_only_for_progressbar_format + get( + :new, + :params => { + :type => 'IssueCustomField', + :custom_field => { + :field_format => 'progressbar' + } + } + ) + assert_response :success + assert_select '[name=?]', 'custom_field[ratio_interval]' do + assert_select 'option[value=?]', '5' + assert_select 'option[value=?][selected=?]', '10', 'selected' + end + end + def test_new_js get( :new, diff --git a/test/functional/documents_controller_test.rb b/test/functional/documents_controller_test.rb index b59ecdc81..944f0b30f 100644 --- a/test/functional/documents_controller_test.rb +++ b/test/functional/documents_controller_test.rb @@ -113,9 +113,9 @@ class DocumentsControllerTest < Redmine::ControllerTest # adds a long description to the first document doc = documents(:documents_001) doc.update(:description => <<~LOREM) - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut egestas, mi vehicula varius varius, ipsum massa fermentum orci, eget tristique ante sem vel mi. Nulla facilisi. Donec enim libero, luctus ac sagittis sit amet, vehicula sagittis magna. Duis ultrices molestie ante, eget scelerisque sem iaculis vitae. Etiam fermentum mauris vitae metus pharetra condimentum fermentum est pretium. Proin sollicitudin elementum quam quis pharetra. Aenean facilisis nunc quis elit volutpat mollis. Aenean eleifend varius euismod. Ut dolor est, congue eget dapibus eget, elementum eu odio. Integer et lectus neque, nec scelerisque nisi. EndOfLineHere + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut egestas, mi vehicula varius varius, ipsum massa fermentum orci, eget tristique ante sem vel mi. Nulla facilisi. Donec enim libero, luctus ac sagittis sit amet, vehicula sagittis magna. Duis ultrices molestie ante, eget scelerisque sem iaculis vitae. Etiam fermentum mauris vitae metus pharetra condimentum fermentum est pretium. Proin sollicitudin elementum quam quis pharetra. Aenean facilisis nunc quis elit volutpat mollis. Aenean eleifend varius euismod. Ut dolor est, congue eget dapibus eget, elementum eu odio. Integer et lectus neque, nec scelerisque nisi. EndOfLineHere - Vestibulum non velit mi. Aliquam scelerisque libero ut nulla fringilla a sollicitudin magna rhoncus. Praesent a nunc lorem, ac porttitor eros. Sed ac diam nec neque interdum adipiscing quis quis justo. Donec arcu nunc, fringilla eu dictum at, venenatis ac sem. Vestibulum quis elit urna, ac mattis sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Vestibulum non velit mi. Aliquam scelerisque libero ut nulla fringilla a sollicitudin magna rhoncus. Praesent a nunc lorem, ac porttitor eros. Sed ac diam nec neque interdum adipiscing quis quis justo. Donec arcu nunc, fringilla eu dictum at, venenatis ac sem. Vestibulum quis elit urna, ac mattis sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit. LOREM get(:index, :params => {:project_id => 'ecookbook'}) assert_response :success diff --git a/test/functional/gantts_controller_test.rb b/test/functional/gantts_controller_test.rb index daba816b8..73a74ba65 100644 --- a/test/functional/gantts_controller_test.rb +++ b/test/functional/gantts_controller_test.rb @@ -58,7 +58,7 @@ class GanttsControllerTest < Redmine::ControllerTest # Assert context menu on issues subject and gantt bar assert_select 'div[class=?]', 'issue-subject hascontextmenu' assert_select 'div.tooltip.hascontextmenu' do - assert_select 'img[class="gravatar"]' + assert_select 'img[class="gravatar avatar"]' end assert_select "form[data-cm-url=?]", '/issues/context_menu' diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 5af5b2797..4b9b44537 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -1737,7 +1737,7 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select 'td.last_notes[colspan="4"]', :text => 'Some notes with Redmine links: #2, r2.' assert_select( 'td.last_notes[colspan="4"]', - :text => 'A comment with inline image: and a reference to #1 and r2.' + :text => 'A comment with inline image: and a reference to #1 and r2.' ) get( :index, @@ -2485,7 +2485,7 @@ class IssuesControllerTest < Redmine::ControllerTest end assert_select 'div#tab-content-history' do assert_select 'div[id=?]', "change-#{Issue.find(1).journals.last.id}" do - assert_select 'ul.details', :text => "Subtask ##{issue.id} added" + assert_select 'ul.journal-details', :text => "Subtask ##{issue.id} added" end end end @@ -2816,7 +2816,7 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select 'h3', {text: /Watchers \(\d*\)/, count: 0} end - def test_show_should_display_watchers_with_gravatars + def test_show_should_display_watchers_with_avatars @request.session[:user_id] = 2 issue = Issue.find(1) issue.add_watcher User.find(2) @@ -2824,9 +2824,10 @@ class IssuesControllerTest < Redmine::ControllerTest with_settings :gravatar_enabled => '1' do get(:show, :params => {:id => 1}) end + assert_select 'div#watchers ul' do assert_select 'li.user-2' do - assert_select 'img.gravatar[title=?]', 'John Smith' + assert_select '.avatar[title=?]', 'John Smith' assert_select 'a[href="/users/2"]' assert_select 'a[class*=delete]' end @@ -3265,6 +3266,22 @@ class IssuesControllerTest < Redmine::ControllerTest end end + def test_show_render_changeset_comments_in_original_context + issue = Issue.find(9) + issue.changeset_ids = [110] + issue.save! + + @request.session[:user_id] = 2 + get :issue_tab, params: {id: issue.id, name: 'changesets', format: 'js'}, xhr: true + + assert_select 'div#changeset-110' do + # assert_select 'div.tabs a[id=?]', 'tab-changesets', text: 'unicorns' + assert_select 'div.changeset-comments' do + assert_select 'a[href=?]', '/projects/ecookbook/wiki/Wiki', text: 'wiki' + end + end + end + def test_show_should_display_spent_time_tab_for_issue_with_time_entries @request.session[:user_id] = 1 get :show, :params => {:id => 3} @@ -3289,7 +3306,7 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select 'a[title=?][href=?]', 'Edit', '/time_entries/3/edit' assert_select 'a[title=?][href=?]', 'Delete', '/time_entries/3' - assert_select 'ul[class=?]', 'details', :text => /1.00 h/ + assert_select 'ul[class=?]', 'journal-details', :text => /1.00 h/ end end @@ -3315,6 +3332,42 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select 'span.badge.badge-private', text: 'Private' end + def test_show_should_display_reactions + current_user = User.generate! + + User.add_to_project(current_user, projects(:projects_001), + Role.generate!(users_visibility: 'members_of_visible_projects', permissions: [:view_issues])) + + @request.session[:user_id] = current_user.id + + get :show, params: { id: 1 } + + assert_response :success + + assert_select 'span[data-reaction-button-id=reaction_issue_1]' do + # The current_user can only see members who belong to projects that the current_user has access to. + # Since the Redmine Admin user does not belong to any projects visible to the current_user, + # the Redmine Admin user's name is not displayed in the reaction user list. Instead, "1 other" is shown. + assert_select 'a.reaction-button[title=?]', 'Dave Lopper and John Smith' do + assert_select 'span.icon-label', '2' + end + end + + assert_select 'span[data-reaction-button-id=reaction_journal_1]' do + assert_select 'a.reaction-button[title=?]', 'John Smith' + end + assert_select 'span[data-reaction-button-id=reaction_journal_2] a.reaction-button' + end + + def test_should_not_display_reactions_when_reactions_feature_is_disabled + with_settings reactions_enabled: '0' do + get :show, params: { id: 1 } + + assert_response :success + assert_select 'span[data-reaction-button-id]', false + end + end + def test_show_should_not_display_edit_attachment_icon_for_user_without_edit_issue_permission_on_tracker role = Role.find(2) role.set_permission_trackers 'edit_issues', [2, 3] @@ -5980,6 +6033,16 @@ class IssuesControllerTest < Redmine::ControllerTest end end + def test_get_edit_with_custom_field_progress_bar + cf = IssueCustomField.generate!(:tracker_ids => [1], :is_for_all => true, :field_format => 'progressbar') + + @request.session[:user_id] = 1 + get(:edit, :params => {:id => 1}) + assert_response :success + + assert_select "select[id=?]", "issue_custom_field_values_#{cf.id}", 1 + end + def test_get_edit_with_me_assigned_to_id @request.session[:user_id] = 2 get( @@ -8635,7 +8698,7 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select 'div#tab-content-history' do assert_select 'div[id=?]', "change-#{parent.journals.last.id}" do - assert_select 'ul.details', :text => "Subtask deleted (##{child.id})" + assert_select 'ul.journal-details', :text => "Subtask deleted (##{child.id})" end end end @@ -8724,31 +8787,27 @@ class IssuesControllerTest < Redmine::ControllerTest assert_select 'a[href=?][onclick=?]', "/issues/1", "", :text => 'Cancel' end - def test_show_should_display_author_gravatar_only_when_not_assigned + def test_show_should_display_author_avatar_only_when_not_assigned issue = Issue.find(1) assert_nil issue.assigned_to_id @request.session[:user_id] = 1 - with_settings :gravatar_enabled => '1' do - get :show, :params => {:id => issue.id} - assert_select 'div.gravatar-with-child' do - assert_select 'img.gravatar', 1 - end + get :show, :params => {:id => issue.id} + assert_select 'div.avatar-with-child' do + assert_select '.avatar', 1 end end - def test_show_should_display_author_and_assignee_gravatars_when_assigned + def test_show_should_display_author_and_assignee_avatars_when_assigned issue = Issue.find(1) issue.assigned_to_id = 2 issue.save! @request.session[:user_id] = 1 - with_settings :gravatar_enabled => '1' do - get :show, :params => {:id => issue.id} - assert_select 'div.gravatar-with-child' do - assert_select 'img.gravatar', 2 - assert_select 'img.gravatar-child', 1 - end + get :show, :params => {:id => issue.id} + assert_select 'div.avatar-with-child' do + assert_select '.avatar', 2 + assert_select '.avatar-child', 1 end end diff --git a/test/functional/issues_custom_fields_visibility_test.rb b/test/functional/issues_custom_fields_visibility_test.rb index 3955ef861..6bcf2ad4f 100644 --- a/test/functional/issues_custom_fields_visibility_test.rb +++ b/test/functional/issues_custom_fields_visibility_test.rb @@ -101,9 +101,9 @@ class IssuesCustomFieldsVisibilityTest < Redmine::ControllerTest get(:show, :params => {:id => @issue.id}) @fields.each_with_index do |field, i| if fields.include?(field) - assert_select 'ul.details i', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} change" + assert_select 'ul.journal-details i', {:text => "Value#{i}", :count => 1}, "User #{user.id} was not able to view #{field.name} change" else - assert_select 'ul.details i', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name} change" + assert_select 'ul.journal-details i', {:text => "Value#{i}", :count => 0}, "User #{user.id} was able to view #{field.name} change" end end end diff --git a/test/functional/messages_controller_test.rb b/test/functional/messages_controller_test.rb index 74b9a3070..cec58ff5f 100644 --- a/test/functional/messages_controller_test.rb +++ b/test/functional/messages_controller_test.rb @@ -28,7 +28,7 @@ class MessagesControllerTest < Redmine::ControllerTest get(:show, :params => {:board_id => 1, :id => 1}) assert_response :success - assert_select 'h2', :text => 'First post' + assert_select 'h2', :text => "RAFirst post" end def test_show_should_contain_reply_field_tags_for_quoting @@ -123,6 +123,27 @@ class MessagesControllerTest < Redmine::ControllerTest assert_select 'h3', {text: /Watchers \(\d*\)/, count: 0} end + def test_show_should_display_reactions + @request.session[:user_id] = 2 + + get :show, params: { board_id: 1, id: 4 } + + assert_response :success + assert_select 'span[data-reaction-button-id=reaction_message_4] a.reaction-button' do + assert_select 'svg use[href*=thumb-up]' + end + assert_select 'span[data-reaction-button-id=reaction_message_5] a.reaction-button' + assert_select 'span[data-reaction-button-id=reaction_message_6] a.reaction-button' + + # Should not display reactions when reactions feature is disabled. + with_settings reactions_enabled: '0' do + get :show, params: { board_id: 1, id: 4 } + + assert_response :success + assert_select 'span[data-reaction-button-id]', false + end + end + def test_get_new @request.session[:user_id] = 2 get(:new, :params => {:board_id => 1}) diff --git a/test/functional/news_controller_test.rb b/test/functional/news_controller_test.rb index f1ddfff71..686fada25 100644 --- a/test/functional/news_controller_test.rb +++ b/test/functional/news_controller_test.rb @@ -75,7 +75,7 @@ class NewsControllerTest < Redmine::ControllerTest get(:show, :params => {:id => 1}) assert_response :success assert_select 'p.breadcrumb a[href=?]', '/projects/ecookbook/news', :text => 'News' - assert_select 'h2', :text => 'eCookbook first release !' + assert_select 'h2', :text => 'JS eCookbook first release !' end def test_show_should_show_attachments @@ -106,6 +106,23 @@ class NewsControllerTest < Redmine::ControllerTest assert_response :not_found end + def test_show_should_display_reactions + @request.session[:user_id] = 1 + + get :show, params: { id: 1 } + assert_response :success + assert_select 'span[data-reaction-button-id=reaction_news_1] a.reaction-button.reacted' + assert_select 'span[data-reaction-button-id=reaction_comment_1] a.reaction-button' + + # Should not display reactions when reactions feature is disabled. + with_settings reactions_enabled: '0' do + get :show, params: { id: 1 } + + assert_response :success + assert_select 'span[data-reaction-button-id]', false + end + end + def test_get_new_with_project_id @request.session[:user_id] = 2 get(:new, :params => {:project_id => 1}) diff --git a/test/functional/reactions_controller_test.rb b/test/functional/reactions_controller_test.rb new file mode 100644 index 000000000..b65794969 --- /dev/null +++ b/test/functional/reactions_controller_test.rb @@ -0,0 +1,394 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- 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_relative '../test_helper' + +class ReactionsControllerTest < Redmine::ControllerTest + setup do + Setting.reactions_enabled = '1' + # jsmith + @request.session[:user_id] = users(:users_002).id + end + + teardown do + Setting.clear_cache + end + + test 'create for issue' do + issue = issues(:issues_002) + + assert_difference( + ->{ Reaction.count } => 1, + ->{ issue.reactions.by(users(:users_002)).count } => 1 + ) do + post :create, params: { + object_type: 'Issue', + object_id: issue.id + }, xhr: true + end + + assert_response :success + end + + test 'create for journal' do + journal = journals(:journals_005) + + assert_difference( + ->{ Reaction.count } => 1, + ->{ journal.reactions.by(users(:users_002)).count } => 1 + ) do + post :create, params: { + object_type: 'Journal', + object_id: journal.id + }, xhr: true + end + + assert_response :success + end + + test 'create for news' do + news = news(:news_002) + + assert_difference( + ->{ Reaction.count } => 1, + ->{ news.reactions.by(users(:users_002)).count } => 1 + ) do + post :create, params: { + object_type: 'News', + object_id: news.id + }, xhr: true + end + + assert_response :success + end + + test 'create reaction for comment' do + comment = comments(:comments_002) + + assert_difference( + ->{ Reaction.count } => 1, + ->{ comment.reactions.by(users(:users_002)).count } => 1 + ) do + post :create, params: { + object_type: 'Comment', + object_id: comment.id + }, xhr: true + end + + assert_response :success + end + + test 'create for message' do + message = messages(:messages_001) + + assert_difference( + ->{ Reaction.count } => 1, + ->{ message.reactions.by(users(:users_002)).count } => 1 + ) do + post :create, params: { + object_type: 'Message', + object_id: message.id + }, xhr: true + end + + assert_response :success + end + + test 'destroy for issue' do + reaction = reactions(:reaction_005) + + assert_difference 'Reaction.count', -1 do + delete :destroy, params: { + id: reaction.id, + # Issue (id=6) + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + + assert_response :success + assert_not Reaction.exists?(reaction.id) + end + + test 'destroy for journal' do + reaction = reactions(:reaction_006) + + assert_difference 'Reaction.count', -1 do + delete :destroy, params: { + id: reaction.id, + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + + assert_response :success + assert_not Reaction.exists?(reaction.id) + end + + test 'destroy for news' do + # For News(id=3) + reaction = reactions(:reaction_010) + + assert_difference 'Reaction.count', -1 do + delete :destroy, params: { + id: reaction.id, + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + + assert_response :success + assert_not Reaction.exists?(reaction.id) + end + + test 'destroy for comment' do + # For Comment(id=1) + reaction = reactions(:reaction_008) + + assert_difference 'Reaction.count', -1 do + delete :destroy, params: { + id: reaction.id, + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + + assert_response :success + assert_not Reaction.exists?(reaction.id) + end + + test 'destroy for message' do + reaction = reactions(:reaction_009) + + assert_difference 'Reaction.count', -1 do + delete :destroy, params: { + id: reaction.id, + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + + assert_response :success + assert_not Reaction.exists?(reaction.id) + end + + test 'create should respond with 403 when feature is disabled' do + Setting.reactions_enabled = '0' + # admin + @request.session[:user_id] = users(:users_001).id + + assert_no_difference 'Reaction.count' do + post :create, params: { + object_type: 'Issue', + object_id: issues(:issues_002).id + }, xhr: true + end + assert_response :forbidden + end + + test 'destroy should respond with 403 when feature is disabled' do + Setting.reactions_enabled = '0' + # admin + @request.session[:user_id] = users(:users_001).id + + reaction = reactions(:reaction_001) + assert_no_difference 'Reaction.count' do + delete :destroy, params: { + id: reaction.id, + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + assert_response :forbidden + end + + test 'create by anonymou user should respond with 401 when feature is disabled' do + Setting.reactions_enabled = '0' + @request.session[:user_id] = nil + + assert_no_difference 'Reaction.count' do + post :create, params: { + object_type: 'Issue', + object_id: issues(:issues_002).id + }, xhr: true + end + assert_response :unauthorized + end + + test 'create by anonymous user should respond with 401' do + @request.session[:user_id] = nil + + assert_no_difference 'Reaction.count' do + post :create, params: { + object_type: 'Issue', + # Issue(id=1) is an issue in a public project + object_id: issues(:issues_001).id + }, xhr: true + end + + assert_response :unauthorized + end + + test 'destroy by anonymous user should respond with 401' do + @request.session[:user_id] = nil + + reaction = reactions(:reaction_002) + assert_no_difference 'Reaction.count' do + delete :destroy, params: { + id: reaction.id, + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + + assert_response :unauthorized + end + + test 'create when reaction already exists should not create a new reaction and succeed' do + assert_no_difference 'Reaction.count' do + post :create, params: { + object_type: 'Comment', + # user(jsmith) has already reacted to Comment(id=1) + object_id: comments(:comments_001).id + }, xhr: true + end + + assert_response :success + end + + test 'destroy another user reaction should not destroy the reaction and succeed' do + # admin user's reaction + reaction = reactions(:reaction_001) + + assert_no_difference 'Reaction.count' do + delete :destroy, params: { + id: reaction.id, + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + + assert_response :success + end + + test 'destroy nonexistent reaction' do + # For Journal(id=4) + reaction = reactions(:reaction_006) + reaction.destroy! + + assert_not Reaction.exists?(reaction.id) + + assert_no_difference 'Reaction.count' do + delete :destroy, params: { + id: reaction.id, + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + + assert_response :success + end + + test 'create with invalid object type should respond with 403' do + # admin + @request.session[:user_id] = users(:users_001).id + + post :create, params: { + object_type: 'InvalidType', + object_id: 1 + }, xhr: true + + assert_response :forbidden + end + + test 'create without permission to view should respond with 403' do + # dlopper + @request.session[:user_id] = users(:users_003).id + + assert_no_difference 'Reaction.count' do + post :create, params: { + object_type: 'Issue', + # dlopper is not a member of the project where the issue (id=4) belongs. + object_id: issues(:issues_004).id + }, xhr: true + end + + assert_response :forbidden + end + + test 'destroy without permission to view should respond with 403' do + # dlopper + @request.session[:user_id] = users(:users_003).id + + # For Issue(id=6) + reaction = reactions(:reaction_005) + + assert_no_difference 'Reaction.count' do + delete :destroy, params: { + id: reaction.id, + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + + assert_response :forbidden + end + + test 'create should respond with 404 for non-JS requests' do + issue = issues(:issues_002) + + assert_no_difference 'Reaction.count' do + post :create, params: { + object_type: 'Issue', + object_id: issue.id + } # Sending an HTML request by omitting xhr: true + end + + assert_response :not_found + end + + test 'create should respond with 403 when project is closed' do + issue = issues(:issues_010) + issue.project.update!(status: Project::STATUS_CLOSED) + + assert_no_difference 'Reaction.count' do + post :create, params: { + object_type: 'Issue', + object_id: issue.id + }, xhr: true + end + + assert_response :forbidden + end + + test 'destroy should respond with 403 when project is closed' do + reaction = reactions(:reaction_005) + reaction.reactable.project.update!(status: Project::STATUS_CLOSED) + + assert_no_difference 'Reaction.count' do + delete :destroy, params: { + id: reaction.id, + object_type: reaction.reactable_type, + object_id: reaction.reactable_id + }, xhr: true + end + + assert_response :forbidden + end +end diff --git a/test/functional/repositories_bazaar_controller_test.rb b/test/functional/repositories_bazaar_controller_test.rb index 2122bf3b9..34e70348d 100644 --- a/test/functional/repositories_bazaar_controller_test.rb +++ b/test/functional/repositories_bazaar_controller_test.rb @@ -37,6 +37,7 @@ class RepositoriesBazaarControllerTest < Redmine::RepositoryControllerTest :log_encoding => 'UTF-8' ) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end if File.directory?(REPOSITORY_PATH) diff --git a/test/functional/repositories_controller_test.rb b/test/functional/repositories_controller_test.rb index beae44f68..7937e59e6 100644 --- a/test/functional/repositories_controller_test.rb +++ b/test/functional/repositories_controller_test.rb @@ -186,6 +186,7 @@ class RepositoriesControllerTest < Redmine::RepositoryControllerTest def test_show_without_main_repository_should_display_first_repository skip unless repository_configured?('subversion') + skip unless Repository::Subversion.scm_available project = Project.find(1) repos = project.repositories @@ -208,6 +209,7 @@ class RepositoriesControllerTest < Redmine::RepositoryControllerTest def test_show_should_show_diff_button_depending_on_browse_repository_permission skip unless repository_configured?('subversion') + skip unless Repository::Subversion.scm_available @request.session[:user_id] = 2 role = Role.find(1) diff --git a/test/functional/repositories_cvs_controller_test.rb b/test/functional/repositories_cvs_controller_test.rb index 558e58d0a..bb30ebc19 100644 --- a/test/functional/repositories_cvs_controller_test.rb +++ b/test/functional/repositories_cvs_controller_test.rb @@ -40,6 +40,7 @@ class RepositoriesCvsControllerTest < Redmine::RepositoryControllerTest :url => MODULE_NAME, :log_encoding => 'UTF-8') assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end if File.directory?(REPOSITORY_PATH) diff --git a/test/functional/repositories_git_controller_test.rb b/test/functional/repositories_git_controller_test.rb index 32fa90e29..cb8788a03 100644 --- a/test/functional/repositories_git_controller_test.rb +++ b/test/functional/repositories_git_controller_test.rb @@ -41,6 +41,7 @@ class RepositoriesGitControllerTest < Redmine::RepositoryControllerTest :path_encoding => 'ISO-8859-1' ) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_create_and_update diff --git a/test/functional/repositories_mercurial_controller_test.rb b/test/functional/repositories_mercurial_controller_test.rb index 4af2fcf99..f5f0e034f 100644 --- a/test/functional/repositories_mercurial_controller_test.rb +++ b/test/functional/repositories_mercurial_controller_test.rb @@ -37,6 +37,8 @@ class RepositoriesMercurialControllerTest < Redmine::RepositoryControllerTest :path_encoding => 'ISO-8859-1' ) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available + @diff_c_support = true end diff --git a/test/functional/repositories_subversion_controller_test.rb b/test/functional/repositories_subversion_controller_test.rb index 60dd213e4..0a430317f 100644 --- a/test/functional/repositories_subversion_controller_test.rb +++ b/test/functional/repositories_subversion_controller_test.rb @@ -34,6 +34,7 @@ class RepositoriesSubversionControllerTest < Redmine::RepositoryControllerTest @repository = Repository::Subversion.create(:project => @project, :url => self.class.subversion_repository_url) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end if repository_configured?('subversion') @@ -357,6 +358,27 @@ class RepositoriesSubversionControllerTest < Redmine::RepositoryControllerTest assert_equal "attachment; filename=\"helloworld.c\"; filename*=UTF-8''helloworld.c", @response.headers['Content-Disposition'] end + def test_entry_should_return_text_plain_for_js_files + # JavaScript files should be served as 'text/plain' instead of + # 'application/javascript' to avoid + # ActionController::InvalidCrossOriginRequest exception + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get( + :raw, + :params => { + :id => PRJ_ID, + :repository_id => @repository.id, + :path => repository_path_hash(['subversion_test', 'foo.js'])[:param] + } + ) + assert_response :success + assert_equal 'text/plain', @response.media_type + assert_match /attachment/, @response.headers['Content-Disposition'] + end + def test_directory_entry assert_equal 0, @repository.changesets.count @repository.fetch_changesets diff --git a/test/functional/roles_controller_test.rb b/test/functional/roles_controller_test.rb index c343b9bd6..cbb2da3de 100644 --- a/test/functional/roles_controller_test.rb +++ b/test/functional/roles_controller_test.rb @@ -239,11 +239,33 @@ class RolesControllerTest < Redmine::ControllerTest assert_nil Role.find_by_id(r.id) end - def test_destroy_role_in_use - delete :destroy, :params => {:id => 1} - assert_redirected_to '/roles' - assert_equal 'This role is in use and cannot be deleted.', flash[:error] - assert_not_nil Role.find_by_id(1) + def test_destroy_role_with_members + role = Role.find(2) # Developer, has members + + delete :destroy, params: { id: role.id } + + assert_redirected_to roles_path + assert Role.find_by(id: role.id) + + assert flash[:error].present? + assert_includes flash[:error], I18n.t(:error_can_not_remove_role) + + expected_dependency_projects = Project.where(identifier: ['ecookbook', 'onlinestore', 'private-child']) + expected_dependency_projects.each do |project| + assert_includes flash[:error], project.name + assert_includes flash[:error], settings_project_path(project, tab: 'members') + end + end + + def test_destroy_builtin + role = Role.anonymous + + delete :destroy, params: { id: role.id } + + assert_redirected_to roles_path + assert Role.find_by(id: role.id) + assert flash[:error].present? + assert_equal flash[:error], I18n.t(:error_can_not_remove_role) end def test_permissions diff --git a/test/functional/search_controller_test.rb b/test/functional/search_controller_test.rb index 1a624c3c7..5e32e5656 100644 --- a/test/functional/search_controller_test.rb +++ b/test/functional/search_controller_test.rb @@ -66,16 +66,18 @@ class SearchControllerTest < Redmine::ControllerTest assert_response :success assert_select '#search-results' do - assert_select 'dt.issue a', :text => /Feature request #2/ + assert_select 'dt.issue a', :text => /Bug #1/ assert_select 'dt.issue a', :text => /Bug #5/ assert_select 'dt.changeset a', :text => /Revision 1/ - assert_select 'dt.issue a', :text => /Add ingredients categories/ - assert_select 'dd', :text => /should be classified by categories/ + assert_select 'dt.issue a', :text => /Cannot print recipes/ + assert_select 'dd', :text => /Unable to print/ end assert_select '#search-results-counts' do - assert_select 'a', :text => 'Changesets (5)' + assert_select 'a', :text => 'Changesets (6)' + assert_select 'a', :text => 'Issues (5)' + assert_select 'a', :text => 'Projects (4)' end end diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index b30559d80..dcdc8d5bb 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -211,6 +211,45 @@ class WorkflowsControllerTest < Redmine::ControllerTest assert w.assignee end + def test_post_edit_with_large_number_of_statuses + # This test ensures that workflows with many statuses can be saved. + # Without setting `ENV['RACK_QUERY_PARSER_PARAMS_LIMIT']`, this raises + # ActionController::BadRequest exception due to exceeding the default + # query parameter limit of 4096. + WorkflowTransition.delete_all + + num_statuses = 40 + transitions_data = {} + + # Allowed statuses for a new issue (status_id = 0) + transitions_data['0'] = {} + (1..num_statuses).each do |status_id| + transitions_data['0'][status_id.to_s] = {'always' => '1'} + end + + # Status transitions between statuses + (1..num_statuses).each do |status_id_from| # rubocop:disable RuboCopStyle/CombinableLoops + transitions_data[status_id_from.to_s] = {} + (1..num_statuses).each do |status_id_to| + # skip self-transitions + next if status_id_from == status_id_to + + transitions_data[status_id_from.to_s][status_id_to.to_s] = { + 'always' => '1', 'author' => '1', 'assignee' => '1' + } + end + end + + assert_nothing_raised do + patch :update, :params => { + :role_id => 2, + :tracker_id => 1, + :transitions => transitions_data + } + end + assert_response :found + end + def test_get_permissions get :permissions diff --git a/test/generators/controller_generator_test.rb b/test/generators/controller_generator_test.rb new file mode 100644 index 000000000..46aeac88e --- /dev/null +++ b/test/generators/controller_generator_test.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- 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_relative '../test_helper' +require_relative '../../lib/generators/redmine_plugin_controller/redmine_plugin_controller_generator' + +class ControllerGeneratorTest < Rails::Generators::TestCase + TMP_DIR = Rails.root / 'tmp/test/generators' + + tests RedminePluginControllerGenerator + destination TMP_DIR + setup :prepare_destination + + setup do + @plugin_directory = Redmine::Plugin.directory + Redmine::Plugin.directory = TMP_DIR + end + + teardown do + Redmine::Plugin.directory = @plugin_directory + end + + def test_generates_files_from_templates + g = generator ['ControllerDemo', 'Todo'] + + assert_name g, 'Todo', :controller + + capture(:stdout) do + g.copy_templates + end + + controller_path_names = (Redmine::Plugin.directory / 'controller_demo/app/controllers') + .glob('*.rb') + assert_equal 1, controller_path_names.count + assert_equal 'todo_controller.rb', controller_path_names.first.basename.to_s + + helper_path_names = (Redmine::Plugin.directory / 'controller_demo/app/helpers') + .glob('*.rb') + assert_equal 1, helper_path_names.count + assert_equal 'todo_helper.rb', helper_path_names.first.basename.to_s + + test_path_names = (Redmine::Plugin.directory / 'controller_demo/test/functional') + .glob('*.rb') + assert_equal 1, test_path_names.count + assert_equal 'todo_controller_test.rb', test_path_names.first.basename.to_s + end + + private + + def assert_name(generator, value, method) + assert_equal value, generator.send(method) + end +end diff --git a/test/generators/model_generator_test.rb b/test/generators/model_generator_test.rb new file mode 100644 index 000000000..f030de6f7 --- /dev/null +++ b/test/generators/model_generator_test.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- 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_relative '../test_helper' +require_relative '../../lib/generators/redmine_plugin_model/redmine_plugin_model_generator' + +class ModelGeneratorTest < Rails::Generators::TestCase + TMP_DIR = Rails.root / 'tmp/test/generators' + + tests RedminePluginModelGenerator + destination TMP_DIR + setup :prepare_destination + + setup do + @plugin_directory = Redmine::Plugin.directory + Redmine::Plugin.directory = TMP_DIR + end + + teardown do + Redmine::Plugin.directory = @plugin_directory + end + + def test_generates_files_from_templates + g = generator ['ModelDemo', 'TodoModel'] + + assert_name g, 'TodoModel', :model + + capture(:stdout) do + g.copy_templates + end + + model_path_names = (Redmine::Plugin.directory / 'model_demo/app/models') + .glob('*.rb') + assert_equal 1, model_path_names.count + assert_equal 'todo_model.rb', model_path_names.first.basename.to_s + + test_path_names = (Redmine::Plugin.directory / 'model_demo/test/unit') + .glob('*.rb') + assert_equal 1, test_path_names.count + assert_equal 'todo_model_test.rb', test_path_names.first.basename.to_s + + migration_path_names = (Redmine::Plugin.directory / 'model_demo/db/migrate') + .glob('*.rb') + assert_equal 1, migration_path_names.count + assert_match(/\d+_create_todo_models\.rb/, migration_path_names.first.basename.to_s) + end + + private + + def assert_name(generator, value, method) + assert_equal value, generator.send(method) + end +end diff --git a/test/helpers/application_helper_test.rb b/test/helpers/application_helper_test.rb index f959744e2..1f60bbbe2 100644 --- a/test/helpers/application_helper_test.rb +++ b/test/helpers/application_helper_test.rb @@ -1732,6 +1732,46 @@ class ApplicationHelperTest < Redmine::HelperTest end end + def test_section_edit_links_with_multiline_heading + raw = <<~RAW + # Wiki + + ## `Foo` Bar + + The heading above generates multiline HTML. + Don't assume heading tags are always single-line. + + ``` + <h2> + <code>Foo</code> Bar</h2> + ``` + RAW + @project = Project.find(1) + set_language_if_valid 'en' + with_settings :text_formatting => 'common_mark' do + result = + textilizable( + raw, + :edit_section_links => + {:controller => 'wiki', :action => 'edit', + :project_id => '1', :id => 'Test'} + ).delete("\n") + + assert_match( + Regexp.new( + '<div class="contextual heading-2" title="Edit this section" id="section-2">' \ + '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=2">' \ + '<svg class="s18 icon-svg" aria-hidden="true"><use href="/assets/icons-.*\.svg#icon--edit"></use></svg>' \ + '<span class="icon-label">Edit this section</span>' \ + '</a></div>' \ + '<a name="Foo-Bar"></a>' \ + '<h2 ><code>Foo</code> Bar<a href="#Foo-Bar" class="wiki-anchor">¶</a></h2>' + ), + result + ) + end + end + def test_default_formatter with_settings :text_formatting => 'unknown' do text = 'a *link*: http://www.example.net/' @@ -2013,20 +2053,6 @@ class ApplicationHelperTest < Redmine::HelperTest end end - def test_principals_check_box_tag_without_avatar - principals = [User.find(1), Group.find(10)] - Setting.gravatar_enabled = '1' - avatar_tags = principals.collect{|p| avatar(p, :size => 16)} - - with_settings :gravatar_enabled => '0' do - tags = principals_check_box_tags(name, principals) - principals.each_with_index do |principal, i| - assert_not_include avatar_tags[i], tags - assert_include content_tag('span', principal_icon(principal), :class => "name icon icon-#{principal.class.name.downcase}"), tags - end - end - end - def test_principals_options_for_select_with_users User.current = nil users = [User.find(2), User.find(4)] @@ -2363,6 +2389,14 @@ class ApplicationHelperTest < Redmine::HelperTest assert_equal expected, format_activity_description(text) end + def test_render_flash_messages_should_ignore_non_string_values + flash[:array_value] = ['1', '2'] + flash[:hash_value] = { foo: 'bar' } + + result = render_flash_messages + assert_equal '', result + end + private def wiki_links_with_special_characters diff --git a/test/helpers/avatars_helper_test.rb b/test/helpers/avatars_helper_test.rb index f407ae09e..6b426bc98 100644 --- a/test/helpers/avatars_helper_test.rb +++ b/test/helpers/avatars_helper_test.rb @@ -63,14 +63,26 @@ class AvatarsHelperTest < Redmine::HelperTest end def test_avatar_css_class - # The default class of the img tag should be gravatar - assert_include 'class="gravatar"', avatar('jsmith <jsmith@somenet.foo>') - assert_include 'class="gravatar picture"', avatar('jsmith <jsmith@somenet.foo>', :class => 'picture') + # The default classes of the img tag should be gravatar and avatar + assert_include 'class="gravatar avatar"', avatar('jsmith <jsmith@somenet.foo>') + assert_include 'class="gravatar avatar picture"', avatar('jsmith <jsmith@somenet.foo>', :class => 'picture') end - def test_avatar_disabled + def test_avatar_with_initials + with_settings :gravatar_default => 'initials' do + assert_include 'initials="RA"', avatar(User.find(1)) + end + end + + def test_avatar_should_reject_initials_if_default_is_not_initials + with_settings :gravatar_default => 'identicon' do + assert_not_include 'initials="RA"', avatar(User.find(1)) + end + end + + def test_avatar_disabled_should_display_user_initials with_settings :gravatar_enabled => '0' do - assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo')) + assert_equal "<span role=\"img\" class=\"avatar-color-2 avatar s24\">JS</span>", avatar(User.find_by_mail('jsmith@somenet.foo')) end end diff --git a/test/helpers/icons_helper_test.rb b/test/helpers/icons_helper_test.rb index ab0b58db4..7ef071f86 100644 --- a/test/helpers/icons_helper_test.rb +++ b/test/helpers/icons_helper_test.rb @@ -71,6 +71,13 @@ class IconsHelperTest < Redmine::HelperTest assert_match expected, icon end + def test_sprite_icon_should_return_svg_with_filled_class_when_style_is_filled + expected = %r{<svg class="s18 icon-svg icon-svg-filled" aria-hidden="true"><use href="/assets/icons-\w+.svg#icon--edit"></use></svg>$} + icon = sprite_icon('edit', style: :filled) + + assert_match expected, icon + end + def test_file_icon_should_return_folder_icon_for_directory entry = stub(:is_dir? => true) expected = %r{<svg class="s18 icon-svg" aria-hidden="true"><use href="/assets/icons-\w+.svg#icon--folder"></use></svg><span class="icon-label">folder_name</span>} diff --git a/test/helpers/journals_helper_test.rb b/test/helpers/journals_helper_test.rb index 355d5ec6f..5c78761ef 100644 --- a/test/helpers/journals_helper_test.rb +++ b/test/helpers/journals_helper_test.rb @@ -47,10 +47,27 @@ class JournalsHelperTest < Redmine::HelperTest journals = issue.visible_journals_with_index # add indice journal_actions = render_journal_actions(issue, journals.first, {reply_links: true}) - assert_select_in journal_actions, 'a[title=?][class="icon icon-comment"]', 'Quote' + assert_select_in journal_actions, 'a[title=?][class="icon-only icon-quote"]', 'Quote' assert_select_in journal_actions, 'a[title=?][class="icon-only icon-edit"]', 'Edit' assert_select_in journal_actions, 'div[class="drdn-items"] a[class="icon icon-del"]' assert_select_in journal_actions, 'div[class="drdn-items"] a[class="icon icon-copy-link"]' + assert_select_in journal_actions, 'span.reaction-button-wrapper' + end + + def test_render_journal_actions_with_journal_without_notes + User.current = User.find(1) + issue = Issue.find(1) + issue.journals.first.update!(notes: '') + + journals = issue.visible_journals_with_index + + journal_actions = render_journal_actions(issue, journals.first, reply_links: true) + + assert_select_in journal_actions, 'span.reaction-button-wrapper' + assert_select_in journal_actions, 'span.drdn' + + assert_select_in journal_actions, 'a[class="icon-only icon-quote"]', false + assert_select_in journal_actions, 'a[class="icon-only icon-edit"]', false end def test_journal_thumbnail_attachments_should_be_in_the_same_order_as_the_journal_details diff --git a/test/helpers/reactions_helper_test.rb b/test/helpers/reactions_helper_test.rb new file mode 100644 index 000000000..1c5c82418 --- /dev/null +++ b/test/helpers/reactions_helper_test.rb @@ -0,0 +1,216 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- 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_relative '../test_helper' + +class ReactionsHelperTest < ActionView::TestCase + include ReactionsHelper + + setup do + User.current = users(:users_002) + Setting.reactions_enabled = '1' + end + + teardown do + Setting.clear_cache + end + + test 'reaction_id_for generates a DOM id' do + assert_equal "reaction_issue_1", reaction_id_for(issues(:issues_001)) + end + + test 'reaction_button returns nil when feature is disabled' do + Setting.reactions_enabled = '0' + + assert_nil reaction_button(issues(:issues_004)) + end + + test 'reaction_button returns nil when object not visible' do + User.current = users(:users_003) + + assert_nil reaction_button(issues(:issues_004)) + end + + test 'reaction_button for anonymous users shows readonly button' do + User.current = nil + + result = reaction_button(journals(:journals_001)) + + assert_select_in result, 'span.reaction-button.readonly[title=?]', 'John Smith' + assert_select_in result, 'a.reaction-button', false + end + + test 'reaction_button for inactive projects shows readonly button' do + issue6 = issues(:issues_006) + issue6.project.update!(status: Project::STATUS_CLOSED) + + result = reaction_button(issue6) + + assert_select_in result, 'span.reaction-button.readonly[title=?]', 'John Smith' + assert_select_in result, 'a.reaction-button', false + end + + test 'reaction_button includes no tooltip when the object has no reactions' do + issue = issues(:issues_002) # Issue without reactions + result = reaction_button(issue) + + assert_select_in result, 'a.reaction-button[title]', false + end + + test 'reaction_button includes tooltip with all usernames when reactions are 10 or fewer' do + issue = issues(:issues_002) + + reactions = build_reactions(10) + issue.reactions += reactions + + result = with_locale 'en' do + reaction_button(issue) + end + + # The tooltip should display usernames in order of newest reactions. + expected_tooltip = 'Bob9 Doe, Bob8 Doe, Bob7 Doe, Bob6 Doe, Bob5 Doe, ' \ + 'Bob4 Doe, Bob3 Doe, Bob2 Doe, Bob1 Doe, and Bob0 Doe' + + assert_select_in result, 'a.reaction-button[title=?]', expected_tooltip + end + + test 'reaction_button includes tooltip with 10 usernames and others count when reactions exceed 10' do + issue = issues(:issues_002) + + reactions = build_reactions(11) + issue.reactions += reactions + + result = with_locale 'en' do + reaction_button(issue) + end + + expected_tooltip = 'Bob10 Doe, Bob9 Doe, Bob8 Doe, Bob7 Doe, Bob6 Doe, ' \ + 'Bob5 Doe, Bob4 Doe, Bob3 Doe, Bob2 Doe, Bob1 Doe, and 1 other' + + assert_select_in result, 'a.reaction-button[title=?]', expected_tooltip + end + + test 'reaction_button should be label less when no reactions' do + issue = issues(:issues_002) + + result = with_locale('en') do + reaction_button(issue) + end + assert_select_in result, 'a.reaction-button' do + assert_select 'span.icon-label', false + end + + # readonly + User.current = nil + result = with_locale('en') do + reaction_button(issue) + end + assert_select_in result, 'span.reaction-button.readonly' do + assert_select 'span.icon-label', false + end + end + + test 'reaction_button should not count and display non-visible users' do + issue2 = issues(:issues_002) + + issue2.reaction_detail = Reaction::Detail.new( + visible_users: users(:users_002, :users_003) + ) + + result = with_locale('en') do + reaction_button(issue2) + end + + assert_select_in result, 'a.reaction-button[title=?]', 'John Smith and Dave Lopper' + + # When all users are non-visible users + issue2.reaction_detail = Reaction::Detail.new( + visible_users: [] + ) + + result = with_locale('en') do + reaction_button(issue2) + end + + assert_select_in result, 'a.reaction-button[title]', false + assert_select_in result, 'a.reaction-button' do + assert_select 'span.icon-label', false + end + end + + test 'reaction_button formats the tooltip content based on the support.array settings of each locale' do + result = with_locale('ja') do + reaction_button(issues(:issues_001)) + end + + assert_select_in result, 'a.reaction-button[title=?]', 'Dave Lopper、John Smith、Redmine Admin' + end + + test 'reaction_button for reacted object' do + User.current = users(:users_002) + + issue = issues(:issues_001) + + result = with_locale('en') do + reaction_button(issue) + end + tooltip = 'Dave Lopper, John Smith, and Redmine Admin' + + assert_select_in result, 'span.reaction-button-wrapper[data-reaction-button-id=?]', 'reaction_issue_1' do + href = reaction_path(issue.reaction_detail.user_reaction, object_type: 'Issue', object_id: 1) + + assert_select 'a.icon.reaction-button.reacted[href=?]', href do + assert_select 'use[href*=?]', 'thumb-up-filled' + assert_select 'span.icon-label', '3' + end + + assert_select 'span.reaction-button', false + end + end + + test 'reaction_button for non-reacted object' do + User.current = users(:users_004) + + issue = issues(:issues_001) + + result = with_locale('en') do + reaction_button(issue) + end + tooltip = 'Dave Lopper, John Smith, and Redmine Admin' + + assert_select_in result, 'span.reaction-button-wrapper[data-reaction-button-id=?]', 'reaction_issue_1' do + href = reactions_path(object_type: 'Issue', object_id: 1) + + assert_select 'a.icon.reaction-button[href=?]', href do + assert_select 'use[href*=?]', 'thumb-up' + assert_select 'span.icon-label', '3' + end + + assert_select 'span.reaction-button', false + end + end + + private + + def build_reactions(count) + Array.new(count) do |i| + Reaction.new(user: User.generate!(firstname: "Bob#{i}")) + end + end +end diff --git a/test/integration/api_test/authentication_test.rb b/test/integration/api_test/authentication_test.rb index 23641b53a..4145fb969 100644 --- a/test/integration/api_test/authentication_test.rb +++ b/test/integration/api_test/authentication_test.rb @@ -127,20 +127,23 @@ class Redmine::ApiTest::AuthenticationTest < Redmine::ApiTest::Base assert_response :unauthorized end + # TODO: check why this test does not use the API endpoint def test_api_should_accept_switch_user_header_for_admin_user user = User.find(1) su = User.find(4) get '/users/current', :headers => {'X-Redmine-API-Key' => user.api_key, 'X-Redmine-Switch-User' => su.login} assert_response :success - assert_select 'h2', :text => su.name + assert_select 'h2', :text => "#{su.initials} #{su.name}" end + # TODO: check why this test does not use the API endpoint def test_api_should_respond_with_412_when_trying_to_switch_to_a_invalid_user get '/users/current', :headers => {'X-Redmine-API-Key' => User.find(1).api_key, 'X-Redmine-Switch-User' => 'foobar'} assert_response :precondition_failed end + # TODO: check why this test does not use the API endpoint def test_api_should_respond_with_412_when_trying_to_switch_to_a_locked_user user = User.find(5) assert user.locked? @@ -149,12 +152,13 @@ class Redmine::ApiTest::AuthenticationTest < Redmine::ApiTest::Base assert_response :precondition_failed end + # TODO: check why this test does not use the API endpoint def test_api_should_not_accept_switch_user_header_for_non_admin_user user = User.find(2) su = User.find(4) get '/users/current', :headers => {'X-Redmine-API-Key' => user.api_key, 'X-Redmine-Switch-User' => su.login} assert_response :success - assert_select 'h2', :text => user.name + assert_select 'h2', :text => "#{user.initials} #{user.name}" end end diff --git a/test/integration/api_test/custom_fields_test.rb b/test/integration/api_test/custom_fields_test.rb index 0df56e59a..4fb06636e 100644 --- a/test/integration/api_test/custom_fields_test.rb +++ b/test/integration/api_test/custom_fields_test.rb @@ -37,6 +37,8 @@ class Redmine::ApiTest::CustomFieldsTest < Redmine::ApiTest::Base end assert_select 'trackers[type=array]' assert_select 'roles[type=array]' + assert_select 'visible', :text => 'true' + assert_select 'editable', :text => 'true' end end end diff --git a/test/integration/api_test/news_test.rb b/test/integration/api_test/news_test.rb index bd9f2bb6d..399b2b347 100644 --- a/test/integration/api_test/news_test.rb +++ b/test/integration/api_test/news_test.rb @@ -62,7 +62,7 @@ class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base assert_select "author[id=2][name=\"John Smith\"]" assert_select 'title', 'eCookbook first release !' assert_select 'summary', 'First version was released...' - assert_select 'description', "eCookbook 1.0 has been released.\n\nVisit http://ecookbook.somenet.foo/" + assert_select 'description', 'eCookbook 1.0 has been released. Visit http://ecookbook.somenet.foo/' assert_select 'created_on', News.find(1).created_on.iso8601 end end diff --git a/test/integration/issues_test.rb b/test/integration/issues_test.rb index ac8d432f1..d0763091e 100644 --- a/test/integration/issues_test.rb +++ b/test/integration/issues_test.rb @@ -355,7 +355,7 @@ class IssuesTest < Redmine::IntegrationTest end # Issue view follow_redirect! - assert_select 'ul.details li', :text => "Tester changed from #{tester} to #{new_tester}" + assert_select 'ul.journal-details li', :text => "Tester changed from #{tester} to #{new_tester}" end end diff --git a/test/integration/repositories_git_test.rb b/test/integration/repositories_git_test.rb index 20d643449..793b49458 100644 --- a/test/integration/repositories_git_test.rb +++ b/test/integration/repositories_git_test.rb @@ -35,6 +35,7 @@ class RepositoriesGitTest < Redmine::IntegrationTest :path_encoding => 'ISO-8859-1' ) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end if File.directory?(REPOSITORY_PATH) diff --git a/test/system/copy_pre_content_to_clipboard_test.rb b/test/system/copy_pre_content_to_clipboard_test.rb new file mode 100644 index 000000000..32ffd4e3e --- /dev/null +++ b/test/system/copy_pre_content_to_clipboard_test.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- 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_relative '../application_system_test_case' +class CopyPreContentToClipboardSystemTest < ApplicationSystemTestCase + def test_copy_issue_pre_content_to_clipboard_if_common_mark + issue = Issue.find(1) + issue.update(description: "```\ntest\ncommon mark\n```") + assert_copied_pre_content_matches(issue_id: issue.id, expected_value: "test\ncommon mark") + end + + def test_copy_issue_code_content_to_clipboard_if_common_mark + issue = Issue.find(1) + issue.update(description: "```ruby\nputs 'Hello, World.'\ncommon mark\n```") + assert_copied_pre_content_matches(issue_id: issue.id, expected_value: "puts 'Hello, World.'\ncommon mark") + end + + def test_copy_issue_pre_content_to_clipboard_if_textile + issue = Issue.find(1) + issue.update(description: "<pre>\ntest\ntextile\n</pre>") + with_settings text_formatting: :textile do + assert_copied_pre_content_matches(issue_id: issue.id, expected_value: "test\ntextile") + end + end + + def test_copy_issue_code_content_to_clipboard_if_textile + issue = Issue.find(1) + issue.update(description: "<pre><code class=\"ruby\">\nputs 'Hello, World.'\ntextile\n</code></pre>") + with_settings text_formatting: :textile do + assert_copied_pre_content_matches(issue_id: issue.id, expected_value: "puts 'Hello, World.'\ntextile") + end + end + + private + + def modifier_key + modifier = osx? ? 'command' : 'control' + modifier.to_sym + end + + def assert_copied_pre_content_matches(issue_id:, expected_value:) + visit "/issues/#{issue_id}" + # A button appears when hovering over the <pre> tag + find("#issue_description_wiki div.pre-wrapper:first-of-type").hover + assert_selector('#issue_description_wiki div.pre-wrapper:first-of-type .copy-pre-content-link') + + # Copy pre content to Clipboard + find("#issue_description_wiki div.pre-wrapper:first-of-type .copy-pre-content-link").click + + # Paste the value copied to the clipboard into the textarea to get and test + first('.icon-edit').click + find('textarea#issue_notes').set('') + find('textarea#issue_notes').send_keys([modifier_key, 'v']) + assert_equal find('textarea#issue_notes').value, expected_value + end +end diff --git a/test/system/issues_reply_test.rb b/test/system/issues_reply_test.rb index 86cd21f16..76ed485a5 100644 --- a/test/system/issues_reply_test.rb +++ b/test/system/issues_reply_test.rb @@ -93,7 +93,7 @@ class IssuesReplyTest < ApplicationSystemTestCase # Select the entire details of the note#1 and the part of the note#1's text. page.execute_script <<-JS const range = document.createRange(); - range.setStartBefore(document.querySelector('#change-1 .details')); + range.setStartBefore(document.querySelector('#change-1 .journal-details')); // Select only the text "Journal" from the text "Journal notes" in the note-1. range.setEnd(document.querySelector('#change-1 .wiki > p').childNodes[0], 7); diff --git a/test/system/issues_test.rb b/test/system/issues_test.rb index 80ef25e0c..c161538e7 100644 --- a/test/system/issues_test.rb +++ b/test/system/issues_test.rb @@ -34,6 +34,8 @@ class IssuesSystemTest < ApplicationSystemTestCase find('input[name=commit]').click end + assert_text /Issue #\d+ created./ + # find created issue issue = Issue.find_by_subject("new test issue") assert_kind_of Issue, issue @@ -86,6 +88,7 @@ class IssuesSystemTest < ApplicationSystemTestCase fill_in field2.name, :with => 'CF2 value' assert_difference 'Issue.count' do page.first(:button, 'Create').click + assert_text /Issue #\d+ created./ end issue = Issue.order('id desc').first @@ -125,6 +128,7 @@ class IssuesSystemTest < ApplicationSystemTestCase end assert_difference 'Issue.count' do find('input[name=commit]').click + assert_text /Issue #\d+ created./ end issue = Issue.order('id desc').first @@ -141,6 +145,7 @@ class IssuesSystemTest < ApplicationSystemTestCase attach_file 'attachments[dummy][file]', Rails.root.join('test/fixtures/files/testfile.txt') fill_in 'attachments[1][description]', :with => 'Some description' click_on 'Create' + assert_text /Issue #\d+ created./ end assert_equal 1, issue.attachments.count assert_equal 'Some description', issue.attachments.first.description @@ -163,6 +168,7 @@ class IssuesSystemTest < ApplicationSystemTestCase attach_file 'attachments[dummy][file]', Rails.root.join('test/fixtures/files/testfile.txt') fill_in 'attachments[1][description]', :with => 'Some description' click_on 'Create' + assert_text /Issue #\d+ created./ end assert_equal 1, issue.attachments.count assert_equal 'Some description', issue.attachments.first.description @@ -181,6 +187,7 @@ class IssuesSystemTest < ApplicationSystemTestCase click_on 'Create' end click_on 'Create' + assert_text /Issue #\d+ created./ end end @@ -200,6 +207,7 @@ class IssuesSystemTest < ApplicationSystemTestCase end assert_difference 'Issue.count' do click_button('Create') + assert_text /Issue #\d+ created./ end issue = Issue.order('id desc').first @@ -230,6 +238,7 @@ class IssuesSystemTest < ApplicationSystemTestCase fill_in 'Form update CF', :with => 'CF value' assert_no_difference 'Issue.count' do page.first(:button, 'Submit').click + assert_text 'Successful update.' end assert page.has_css?('#flash_notice') issue = Issue.find(1) @@ -245,6 +254,7 @@ class IssuesSystemTest < ApplicationSystemTestCase page.find("#issue_status_id").select("Closed") assert_no_difference 'Issue.count' do page.first(:button, 'Submit').click + assert_text 'Successful update.' end assert page.has_css?('#flash_notice') assert_equal 5, issue.reload.status.id @@ -267,6 +277,7 @@ class IssuesSystemTest < ApplicationSystemTestCase click_on 'Submit' + assert_text 'Successful update.' assert_equal 3, Issue.find(2).attachments.count end diff --git a/test/system/messages_test.rb b/test/system/messages_test.rb index 66a29a3a7..ac074ac1a 100644 --- a/test/system/messages_test.rb +++ b/test/system/messages_test.rb @@ -22,7 +22,7 @@ require_relative '../application_system_test_case' class MessagesTest < ApplicationSystemTestCase def test_reply_to_topic_message with_text_formatting 'common_mark' do - within '#content > .contextual' do + within '#content > [data-controller="quote-reply"]' do click_link 'Quote' end @@ -64,7 +64,7 @@ class MessagesTest < ApplicationSystemTestCase window.getSelection().addRange(range); JS - within '#content > .contextual' do + within '#content > [data-controller="quote-reply"]' do click_link 'Quote' end diff --git a/test/system/oauth_provider_test.rb b/test/system/oauth_provider_test.rb new file mode 100644 index 000000000..364ed4c94 --- /dev/null +++ b/test/system/oauth_provider_test.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require_relative '../application_system_test_case' +require 'oauth2' +require 'rack' +require 'puma' + +class OauthProviderSystemTest < ApplicationSystemTestCase + fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, + :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues, + :enumerations, :custom_fields, :custom_values, :custom_fields_trackers, + :watchers, :journals, :journal_details, :versions, + :workflows + + test 'application creation and authorization' do + # + # admin creates the application, granting permissions and generating a uuid + # and secret. + # + log_user 'admin', 'admin' + with_settings rest_api_enabled: 1 do + visit '/admin' + within 'div#admin-menu ul' do + click_link 'Applications' + end + click_link 'New Application' + fill_in 'Name', with: 'Oauth Test' + + # as per https://tools.ietf.org/html/rfc8252#section-7.3, the port can be + # anything when the redirect URI's host is 127.0.0.1. + fill_in 'Redirect URI', with: 'http://127.0.0.1' + + check 'View Issues' + click_button 'Create' + + assert_text "Application created." + end + + assert app = Doorkeeper::Application.find_by_name('Oauth Test') + + find 'h2', visible: true, text: /Oauth Test/ + find 'p code', visible: true, text: app.uid + find 'p strong', visible: true, text: /will not be shown again/ + find 'p code', visible: true, text: /View Issues/ + + # scrape the clear text secret from the page + app_secret = all(:css, 'p code')[1].text + + click_link 'Sign out' + + # + # regular user authorizes the application + # + client = OAuth2::Client.new(app.uid, app_secret, site: "http://127.0.0.1:#{test_port}/") + + # set up a dummy http listener to handle the redirect + port = rand 10000..20000 + redirect_uri = "http://127.0.0.1:#{port}" + # the request handler below will set this to the auth token + token = nil + + # launches webrick, listening for the redirect with the auth code. + launch_client_app(port: port) do |req, res| + # get access code from code url param + if code = req.params['code'].presence + # exchange it for token + token = client.auth_code.get_token(code, redirect_uri: redirect_uri) + res.body = ["<html><body><p>Authorization succeeded, you may close this window now.</p></body></html>"] + end + end + + log_user 'jsmith', 'jsmith' + with_settings rest_api_enabled: 1 do + visit '/my/account' + click_link 'Authorized applications' + find 'p.nodata', visible: true + + # an oauth client would send the user to this url to request permission + url = client.auth_code.authorize_url redirect_uri: redirect_uri, scope: 'view_issues view_project' + uri = URI.parse url + visit uri.path + '?' + uri.query + + find 'h2', visible: true, text: 'Authorization required' + find 'p', visible: true, text: /Authorize Oauth Test/ + find '.oauth-permissions', visible: true, text: /View Issues/ + find '.oauth-permissions', visible: true, text: /View project/ + + click_button 'Authorize' + + assert grant = app.access_grants.last + assert_equal 'view_issues view_project', grant.scopes.to_s + + # check for output defined above in the request handler + find 'p', visible: true, text: /Authorization succeeded/ + assert token.present? + + visit '/my/account' + click_link 'Authorized applications' + find 'td', visible: true, text: /Oauth Test/ + click_link 'Sign out' + + # Now, use the token for some API requests + assert_raise(RestClient::Unauthorized) do + RestClient.get "http://localhost:#{test_port}/projects/onlinestore/issues.json" + end + + headers = { 'Authorization' => "Bearer #{token.token}" } + r = RestClient.get "http://localhost:#{test_port}/projects/onlinestore/issues.json", headers + issues = JSON.parse(r.body)['issues'] + assert issues.any? + + # time entries access is not part of the granted scopes + assert_raise(RestClient::Forbidden) do + RestClient.get "http://localhost:#{test_port}/projects/onlinestore/time_entries.json", headers + end + end + end + + private + + def launch_client_app(port: 12345, path: '/', &block) + app = ->(env) do + req = Rack::Request.new(env) + res = Rack::Response.new + yield(req, res) + res.finish + end + + server = Puma::Server.new app + server.add_tcp_listener '127.0.0.1', port + Thread.new { server.run } + end + + def test_port + Capybara.current_session.server.port + end +end diff --git a/test/system/reactions_test.rb b/test/system/reactions_test.rb new file mode 100644 index 000000000..96dd4cf81 --- /dev/null +++ b/test/system/reactions_test.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- 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_relative '../application_system_test_case' + +class ReactionsSystemTest < ApplicationSystemTestCase + def test_react_to_issue + log_user('jsmith', 'jsmith') + + issue = issues(:issues_002) + + with_settings(reactions_enabled: '1') do + visit '/issues/2' + reaction_button = find("div.issue.details [data-reaction-button-id=\"reaction_issue_#{issue.id}\"]") + assert_reaction_add_and_remove(reaction_button, issue) + end + end + + def test_react_to_journal + log_user('jsmith', 'jsmith') + + journal = journals(:journals_002) + + with_settings(reactions_enabled: '1') do + visit '/issues/1' + reaction_button = find("[data-reaction-button-id=\"reaction_journal_#{journal.id}\"]") + assert_reaction_add_and_remove(reaction_button, journal.reload) + end + end + + def test_react_to_forum_reply + log_user('jsmith', 'jsmith') + + reply_message = messages(:messages_002) # reply to message_001 + + with_settings(reactions_enabled: '1') do + visit 'boards/1/topics/1' + reaction_button = find("[data-reaction-button-id=\"reaction_message_#{reply_message.id}\"]") + assert_reaction_add_and_remove(reaction_button, reply_message) + end + end + + def test_react_to_forum_message + log_user('jsmith', 'jsmith') + + message = messages(:messages_001) + + with_settings(reactions_enabled: '1') do + visit 'boards/1/topics/1' + reaction_button = find("[data-reaction-button-id=\"reaction_message_#{message.id}\"]") + assert_reaction_add_and_remove(reaction_button, message) + end + end + + def test_react_to_news + log_user('jsmith', 'jsmith') + + with_settings(reactions_enabled: '1') do + visit '/news/2' + reaction_button = find("[data-reaction-button-id=\"reaction_news_2\"]") + assert_reaction_add_and_remove(reaction_button, news(:news_002)) + end + end + + def test_react_to_comment + log_user('jsmith', 'jsmith') + + comment = comments(:comments_002) + + with_settings(reactions_enabled: '1') do + visit '/news/1' + reaction_button = find("[data-reaction-button-id=\"reaction_comment_#{comment.id}\"]") + assert_reaction_add_and_remove(reaction_button, comment) + end + end + + def test_reactions_disabled + log_user('jsmith', 'jsmith') + + with_settings(reactions_enabled: '0') do + visit '/issues/1' + assert_no_selector('[data-reaction-button-id="reaction_issue_1"]') + end + end + + def test_reaction_button_is_visible_but_not_clickable_for_not_logged_in_user + with_settings(reactions_enabled: '1') do + visit '/issues/1' + + # visible + reaction_button = find('div.issue.details [data-reaction-button-id="reaction_issue_1"]') + within(reaction_button) { assert_selector('span.reaction-button') } + assert_equal "3", reaction_button.text + + # not clickable + within(reaction_button) { assert_no_selector('a.reaction-button') } + end + end + + def test_reaction_button_is_visible_on_property_changes_tab + # Create a journal with no notes + journal_without_notes = Journal.generate!(journalized: issues(:issues_001), notes: '', details: [JournalDetail.new]) + + log_user('jsmith', 'jsmith') + + visit '/issues/1?tab=properties' + + # Scroll to the history content + click_link '#1' + + assert_selector '#tab-properties.selected' + + within('#change-1') do + assert_selector 'a.reaction-button' + + assert_no_selector 'a.icon-quote' + assert_no_selector 'span.drdn' + end + within("#change-#{journal_without_notes.id}") do + assert_selector 'a.reaction-button' + + assert_no_selector '.drdn' + end + + click_link 'History' + + within('#change-1') do + assert_selector 'a.reaction-button' + + assert_selector 'a.icon-quote' + assert_selector 'span.drdn' + end + within("#change-#{journal_without_notes.id}") do + assert_selector 'a.reaction-button' + assert_selector 'span.drdn' + + assert_no_selector 'a.icon-quote' + end + end + + private + + def assert_reaction_add_and_remove(reaction_button, expected_subject) + # Add a reaction + within(reaction_button) { find('a.reaction-button').click } + find('body').hover # Hide tooltip + within(reaction_button) { assert_selector('a.reaction-button.reacted[title="John Smith"]') } + assert_equal "1", reaction_button.text + assert_equal 1, expected_subject.reactions.count + + # Remove the reaction + within(reaction_button) { find('a.reacted').click } + within(reaction_button) { assert_selector('a.reaction-button:not(.reacted)') } + assert_equal "", reaction_button.text + assert_equal 0, expected_subject.reactions.count + end +end diff --git a/test/system/sticky_issue_header_test.rb b/test/system/sticky_issue_header_test.rb new file mode 100644 index 000000000..5c67b36f3 --- /dev/null +++ b/test/system/sticky_issue_header_test.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- 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_relative '../application_system_test_case' +class StickyIssueHeaderSystemTest < ApplicationSystemTestCase + test "sticky issue header is hidden by default" do + issue = Issue.find(1) + visit issue_path(issue) + + assert_no_selector "#sticky-issue-header", text: issue.subject + end + + test "sticky issue header appears on scroll" do + issue = Issue.find(2) + visit issue_path(issue) + + page.execute_script("window.scrollTo(0, 1000)") + assert_selector "#sticky-issue-header.is-visible", text: issue.subject + + page.execute_script("window.scrollTo(0, 0)") + assert_no_selector "#sticky-issue-header", text: issue.subject + end +end diff --git a/test/system/sudo_mode_test.rb b/test/system/sudo_mode_test.rb index 73e755acd..307d465ff 100644 --- a/test/system/sudo_mode_test.rb +++ b/test/system/sudo_mode_test.rb @@ -48,7 +48,6 @@ class SudoModeSystemTest < ApplicationSystemTestCase find('input[name=commit]').click end - assert_equal '/users', current_path assert page.has_content?("Confirm your password to continue") assert page.has_css?('form#sudo-form') @@ -56,6 +55,8 @@ class SudoModeSystemTest < ApplicationSystemTestCase fill_in 'Password', :with => 'admin' click_button 'Submit' end + + assert_text /User johnpaul created./ end end diff --git a/test/system/timelog_test.rb b/test/system/timelog_test.rb index 57c521096..38c3ae19c 100644 --- a/test/system/timelog_test.rb +++ b/test/system/timelog_test.rb @@ -49,6 +49,8 @@ class TimelogTest < ApplicationSystemTestCase select 'QA', :from => 'Activity' page.first(:button, 'Submit').click + assert_text 'Successful update.' + entries = TimeEntry.where(:id => [1, 2, 3]).to_a assert entries.all? {|entry| entry.hours == 8.5} assert entries.all? {|entry| entry.activity.name == 'QA'} @@ -89,6 +91,7 @@ class TimelogTest < ApplicationSystemTestCase select 'Tracker', :from => 'Available Columns' page.first('input[type=button].move-right').click click_on 'Save' + assert_text 'Successful update.' # Display the list with updated settings visit '/time_entries' diff --git a/test/unit/changeset_test.rb b/test/unit/changeset_test.rb index ca1e010e3..3ad8b1cbf 100644 --- a/test/unit/changeset_test.rb +++ b/test/unit/changeset_test.rb @@ -479,7 +479,7 @@ class ChangesetTest < ActiveSupport::TestCase end def test_next_nil - changeset = Changeset.find_by_revision('10') + changeset = Changeset.find_by_revision('11') assert_nil changeset.next end diff --git a/test/unit/email_address_test.rb b/test/unit/email_address_test.rb index 9d57beb97..923df897a 100644 --- a/test/unit/email_address_test.rb +++ b/test/unit/email_address_test.rb @@ -63,6 +63,12 @@ class EmailAddressTest < ActiveSupport::TestCase end end + def test_domain_in_should_not_raise_exception_when_domain_is_nil + assert_nothing_raised do + assert_not EmailAddress.domain_in?(nil, 'example.com') + end + end + def test_should_reject_invalid_email assert_not EmailAddress.new(address: 'invalid,email@example.com').valid? end diff --git a/test/unit/lib/redmine/field_format/progressbar_format_test.rb b/test/unit/lib/redmine/field_format/progressbar_format_test.rb index d507f9ddb..6e0df724d 100644 --- a/test/unit/lib/redmine/field_format/progressbar_format_test.rb +++ b/test/unit/lib/redmine/field_format/progressbar_format_test.rb @@ -21,7 +21,7 @@ require_relative '../../../../test_helper' require 'redmine/field_format' module Redmine::FieldFormat - class ProgressbarFormatTest < ActiveSupport::TestCase + class ProgressbarFormatTest < ActionView::TestCase def setup @field = IssueCustomField.new(name: 'ProgressbarTest', field_format: 'progressbar') @format = Redmine::FieldFormat::ProgressbarFormat.instance @@ -81,5 +81,52 @@ module Redmine::FieldFormat options = @format.query_filter_options(@field, nil) assert_equal :integer, options[:type] end + + def test_default_ratio_interval_should_be_default_issue_done_ratio_interval + @field.save + assert_equal 10, @field.ratio_interval + end + + def test_ratio_interval + @field.update(ratio_interval: 5) + assert_equal 5, @field.ratio_interval + end + + def test_edit_tag_possible_values_with_ratio_interval + [5, 10].each do |ratio_interval| + @field.update(ratio_interval: ratio_interval) + value = CustomValue.new(custom_field: @field, value: '90') + + tag = @field.format.edit_tag(self, 'id', 'name', value) + assert_select_in tag, 'select' do + assert_select 'option', 100 / ratio_interval + 1 + end + end + end + + def test_bulk_edit_tag_possible_values_with_ratio_interval + [5, 10].each do |ratio_interval| + @field.update(ratio_interval: ratio_interval) + value = CustomValue.new(custom_field: @field, value: '90') + objects = [Issue.new, Issue.new] + + tag = @field.format.bulk_edit_tag(self, 'id', 'name', @field, objects, value) + assert_select_in tag, 'select' do |select| + assert_select select.first, 'option', 100 / ratio_interval + 2 + end + end + end + + def test_formatted_value_with_html_true + expected = progress_bar(50) + formatted = @format.formatted_value(self, @field, 50, Issue.new, true) + assert_equal expected, formatted + assert formatted.html_safe? + end + + def test_formatted_value_with_html_false + formatted = @format.formatted_value(self, @field, 50, Issue.new, false) + assert_equal '50', formatted + end end end diff --git a/test/unit/lib/redmine/quote_reply_helper_test.rb b/test/unit/lib/redmine/quote_reply_helper_test.rb index 43adb521b..d5d13d4f8 100644 --- a/test/unit/lib/redmine/quote_reply_helper_test.rb +++ b/test/unit/lib/redmine/quote_reply_helper_test.rb @@ -23,18 +23,18 @@ class QuoteReplyHelperTest < ActionView::TestCase include ERB::Util include Redmine::QuoteReply::Helper - def test_quote_reply + def test_quote_reply_button with_locale 'en' do url = quoted_issue_path(issues(:issues_001)) - a_tag = quote_reply(url, '#issue_description_wiki') - assert_includes a_tag, %|onclick="#{h "quoteReply('/issues/1/quoted', '#issue_description_wiki', 'common_mark'); return false;"}"| - assert_includes a_tag, %|class="icon icon-comment"| - assert_not_includes a_tag, 'title=' + html = quote_reply_button(url: url) + assert_select_in html, + 'a[data-quote-reply-url-param=?][data-quote-reply-text-formatting-param=?]:not([title])', + url, Setting.text_formatting # When icon_only is true - a_tag = quote_reply(url, '#issue_description_wiki', icon_only: true) - assert_includes a_tag, %|title="Quote"| + html = quote_reply_button(url: url, icon_only: true) + assert_select_in html, 'a.icon-only.icon-quote[title=?]', 'Quote' end end end diff --git a/test/unit/lib/redmine/reaction_test.rb b/test/unit/lib/redmine/reaction_test.rb new file mode 100644 index 000000000..f3228a3bd --- /dev/null +++ b/test/unit/lib/redmine/reaction_test.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- 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_relative '../../../test_helper' + +class Redmine::ReactionTest < ActiveSupport::TestCase + setup do + @user = users(:users_002) + @issue = issues(:issues_007) + Setting.reactions_enabled = '1' + end + + teardown do + Setting.clear_cache + end + + test 'preload_reaction_details preloads ReactionDetail for all objects in the collection' do + User.current = users(:users_002) + + issue1 = issues(:issues_001) + issue2 = issues(:issues_002) + + assert_nil issue1.instance_variable_get(:@reaction_detail) + assert_nil issue2.instance_variable_get(:@reaction_detail) + + Issue.preload_reaction_details([issue1, issue2]) + + expected_issue1_reaction_detail = Reaction::Detail.new( + visible_users: [users(:users_003), users(:users_002), users(:users_001)], + user_reaction: reactions(:reaction_002) + ) + + # ReactionDetail is already preloaded, so calling reaction_detail does not execute any query. + assert_no_queries do + assert_equal expected_issue1_reaction_detail, issue1.reaction_detail + + # Even when an object has no reactions, an empty ReactionDetail is set. + assert_equal Reaction::Detail.new( + visible_users: [], + user_reaction: nil + ), issue2.reaction_detail + end + end + + test 'visible_users in ReactionDetail preloaded by preload_reaction_details does not include non-visible users' do + current_user = User.current = User.generate! + visible_user = users(:users_002) + non_visible_user = User.generate! + + project = Project.generate! + role = Role.generate!(users_visibility: 'members_of_visible_projects') + + User.add_to_project(current_user, project, role) + User.add_to_project(visible_user, project, roles(:roles_001)) + + issue = Issue.generate!(project: project) + + [current_user, visible_user, non_visible_user].each do |user| + issue.reactions.create!(user: user) + end + + Issue.preload_reaction_details([issue]) + + # non_visible_user is not visible to current_user because they do not belong to any project. + assert_equal [visible_user, current_user], issue.reaction_detail.visible_users + end + + test 'preload_reaction_details does nothing when the reaction feature is disabled' do + Setting.reactions_enabled = '0' + + User.current = users(:users_002) + news1 = news(:news_001) + + # Stub the Setting to avoid executing queries for retrieving settings, + # making it easier to confirm no queries are executed by preload_reaction_details(). + Setting.stubs(:reactions_enabled?).returns(false) + + assert_no_queries do + News.preload_reaction_details([news1]) + end + + assert_nil news1.instance_variable_get(:@reaction_detail) + end + + test 'reaction_detail loads and returns ReactionDetail if it is not preloaded' do + message7 = messages(:messages_007) + + User.current = users(:users_002) + assert_nil message7.instance_variable_get(:@reaction_detail) + + assert_equal Reaction::Detail.new( + visible_users: [users(:users_002)], + user_reaction: reactions(:reaction_009) + ), message7.reaction_detail + end + + test 'load_reaction_detail loads ReactionDetail for the object itself' do + comment1 = comments(:comments_001) + + User.current = users(:users_001) + assert_nil comment1.instance_variable_get(:@reaction_detail) + + comment1.load_reaction_detail + + assert_equal Reaction::Detail.new( + visible_users: [users(:users_002)], + user_reaction: nil + ), comment1.reaction_detail + end + + test 'visible? returns true when reactions are enabled and object is visible to user' do + object = issues(:issues_007) + user = users(:users_002) + + assert Redmine::Reaction.visible?(object, user) + end + + test 'visible? returns false when reactions are disabled' do + Setting.reactions_enabled = '0' + + object = issues(:issues_007) + user = users(:users_002) + + assert_not Redmine::Reaction.visible?(object, user) + end + + test 'visible? returns false when object is not visible to user' do + object = issues(:issues_007) + user = users(:users_002) + + object.expects(:visible?).with(user).returns(false) + + assert_not Redmine::Reaction.visible?(object, user) + end + + test 'editable? returns true for various reactable objects when user is logged in, object is visible, and project is active' do + reactable_objects = { + issue: issues(:issues_007), + message: messages(:messages_001), + news: news(:news_001), + journal: journals(:journals_001), + comment: comments(:comments_002) + } + user = users(:users_002) + + reactable_objects.each do |type, object| + assert Redmine::Reaction.editable?(object, user), "Expected editable? to return true for #{type}" + end + end + + test 'editable? returns false when user is not logged in' do + object = issues(:issues_007) + user = User.anonymous + + assert_not Redmine::Reaction.editable?(object, user) + end + + test 'editable? returns false when project is inactive' do + object = issues(:issues_007) + user = users(:users_002) + object.project.update!(status: Project::STATUS_ARCHIVED) + + assert_not Redmine::Reaction.editable?(object, user) + end + + test 'editable? returns false when project is closed' do + object = issues(:issues_007) + user = users(:users_002) + object.project.update!(status: Project::STATUS_CLOSED) + + assert_not Redmine::Reaction.editable?(object, user) + end +end diff --git a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb index c0bff9b1f..9d6cd6b32 100644 --- a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb @@ -27,6 +27,7 @@ class BazaarAdapterTest < ActiveSupport::TestCase def setup @adapter = Redmine::Scm::Adapters::BazaarAdapter. new(File.join(REPOSITORY_PATH, "trunk")) + skip "SCM command is unavailable" unless @adapter.class.client_available end def test_scm_version diff --git a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb index 2ed9dc618..3bfe24997 100644 --- a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb @@ -27,6 +27,7 @@ class CvsAdapterTest < ActiveSupport::TestCase if File.directory?(REPOSITORY_PATH) def setup @adapter = Redmine::Scm::Adapters::CvsAdapter.new(MODULE_NAME, REPOSITORY_PATH) + skip "SCM command is unavailable" unless @adapter.class.client_available end def test_scm_version diff --git a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb index bf054860a..3f0451601 100644 --- a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb @@ -42,13 +42,6 @@ class GitAdapterTest < ActiveSupport::TestCase WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10" def setup - adapter_class = Redmine::Scm::Adapters::GitAdapter - assert adapter_class - assert adapter_class.client_command - assert_equal true, adapter_class.client_available - assert_equal true, adapter_class.client_version_above?([1]) - assert_equal true, adapter_class.client_version_above?([1, 0]) - @adapter = Redmine::Scm::Adapters::GitAdapter. new( @@ -59,6 +52,8 @@ class GitAdapterTest < ActiveSupport::TestCase 'ISO-8859-1' ) assert @adapter + skip "SCM is unavailable" unless @adapter.class.client_available + @char_1 = 'Ü' @str_felix_hex = "Felix Sch\xC3\xA4fer".b end diff --git a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb index b4f284103..81741a746 100644 --- a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb @@ -30,12 +30,6 @@ class MercurialAdapterTest < ActiveSupport::TestCase if File.directory?(REPOSITORY_PATH) def setup - adapter_class = Redmine::Scm::Adapters::MercurialAdapter - assert adapter_class - assert adapter_class.client_command - assert_equal true, adapter_class.client_available - assert_equal true, adapter_class.client_version_above?([0, 9, 5]) - @adapter = Redmine::Scm::Adapters::MercurialAdapter.new( REPOSITORY_PATH, @@ -44,6 +38,8 @@ class MercurialAdapterTest < ActiveSupport::TestCase nil, 'ISO-8859-1' ) + skip "SCM command is unavailable" unless @adapter.class.client_available + @diff_c_support = true @char_1 = 'Ü' @tag_char_1 = 'tag-Ü-00' diff --git a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb index fe574a4ff..edc3541d1 100644 --- a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb @@ -23,6 +23,7 @@ class SubversionAdapterTest < ActiveSupport::TestCase if repository_configured?('subversion') def setup @adapter = Redmine::Scm::Adapters::SubversionAdapter.new(self.class.subversion_repository_url) + skip "SCM command is unavailable" unless @adapter.class.client_available end def test_client_version diff --git a/test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb b/test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb index 5214a1e00..bb0c5d450 100644 --- a/test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb +++ b/test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb @@ -26,71 +26,71 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase @formatter = Redmine::WikiFormatting::CommonMark::Formatter end - def format(text) + def to_html(text) @formatter.new(text).to_html end def test_should_render_hard_breaks html ="<p>foo<br>\nbar</p>" - assert_equal html, format("foo\\\nbar") - assert_equal html, format("foo \nbar") + assert_equal html, to_html("foo\\\nbar") + assert_equal html, to_html("foo \nbar") end def test_should_render_soft_breaks - assert_equal "<p>foo<br>\nbar</p>", format("foo\nbar") + assert_equal "<p>foo<br>\nbar</p>", to_html("foo\nbar") end def test_syntax_error_in_image_reference_should_not_raise_exception - assert format("!>[](foo.png)") + assert to_html("!>[](foo.png)") end def test_empty_image_should_not_raise_exception - assert format("![]()") + assert to_html("![]()") end def test_inline_style - assert_equal "<p><strong>foo</strong></p>", format("**foo**") + assert_equal "<p><strong>foo</strong></p>", to_html("**foo**") end def test_not_set_intra_emphasis - assert_equal "<p>foo_bar_baz</p>", format("foo_bar_baz") + assert_equal "<p>foo_bar_baz</p>", to_html("foo_bar_baz") end def test_wiki_links_should_be_preserved text = 'This is a wiki link: [[Foo]]' - assert_include '[[Foo]]', format(text) + assert_include '[[Foo]]', to_html(text) end def test_redmine_links_with_double_quotes_should_be_preserved text = 'This is a redmine link: version:"1.0"' - assert_include 'version:"1.0"', format(text) + assert_include 'version:"1.0"', to_html(text) end def test_links_by_id_should_be_preserved text = "[project#3]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) end def test_links_to_users_should_be_preserved text = "[@login]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "[user:login]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "user:user@example.org" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "[user:user@example.org]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "@user@example.org" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "[@user@example.org]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) end def test_files_with_at_should_not_end_up_as_mailto_links text = "printscreen@2x.png" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "[printscreen@2x.png]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) end def test_should_support_syntax_highlight @@ -100,7 +100,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase end ~~~ STR - assert_select_in format(text), 'pre code.ruby.syntaxhl' do + assert_select_in to_html(text), 'pre code.ruby.syntaxhl' do assert_select 'span.k', :text => 'def' assert_select "[data-language='ruby']" end @@ -114,7 +114,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase ~~~ STR - assert_select_in format(text), 'pre' do + assert_select_in to_html(text), 'pre' do assert_select 'code[class=?]', "c++ syntaxhl" assert_select 'span.kt', :text => 'int' assert_select "[data-language=?]", "c++" @@ -123,12 +123,12 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase def test_external_links_should_have_external_css_class text = 'This is a [link](http://example.net/)' - assert_equal '<p>This is a <a href="http://example.net/" class="external">link</a></p>', format(text) + assert_equal '<p>This is a <a href="http://example.net/" class="external">link</a></p>', to_html(text) end def test_locals_links_should_not_have_external_css_class text = 'This is a [link](/issues)' - assert_equal '<p>This is a <a href="/issues">link</a></p>', format(text) + assert_equal '<p>This is a <a href="/issues">link</a></p>', to_html(text) end def test_markdown_should_not_require_surrounded_empty_line @@ -137,7 +137,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase * One * Two STR - assert_equal "<p>This is a list:</p>\n<ul>\n<li>One</li>\n<li>Two</li>\n</ul>", format(text) + assert_equal "<p>This is a list:</p>\n<ul>\n<li>One</li>\n<li>Two</li>\n</ul>", to_html(text) end def test_footnotes @@ -156,46 +156,46 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase </ol> EXPECTED - assert_equal expected.gsub(%r{[\r\n\t]}, ''), format(text).gsub(%r{[\r\n\t]}, '').rstrip + assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '').rstrip end STR_WITH_PRE = [ # 0 <<~STR.chomp, # Title - + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero. STR # 1 <<~STR.chomp, ## Heading 2 - + ~~~ruby def foo end ~~~ - + Morbi facilisis accumsan orci non pharetra. - + ~~~ ruby def foo end ~~~ - + ``` Pre Content: - + ## Inside pre - + <tag> inside pre block - + Morbi facilisis accumsan orci non pharetra. ``` STR # 2 <<~STR.chomp, ### Heading 3 - + Nulla nunc nisi, egestas in ornare vel, posuere ac libero. STR ] @@ -226,18 +226,18 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase text = STR_WITH_PRE.join("\n\n") replacement = "New text" - assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"), + assert_equal [STR_WITH_PRE[0..1], "New text"].join("\n\n"), @formatter.new(text).update_section(3, replacement) end def test_should_emphasize_text text = 'This _text_ should be emphasized' - assert_equal '<p>This <em>text</em> should be emphasized</p>', format(text) + assert_equal '<p>This <em>text</em> should be emphasized</p>', to_html(text) end def test_should_strike_through_text text = 'This ~~text~~ should be striked through' - assert_equal '<p>This <del>text</del> should be striked through</p>', format(text) + assert_equal '<p>This <del>text</del> should be striked through</p>', to_html(text) end def test_should_autolink_urls_and_emails @@ -249,13 +249,13 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase ["www.example.org", '<p><a href="http://www.example.org" class="external">www.example.org</a></p>'], ["user@example.org", '<p><a href="mailto:user@example.org" class="email">user@example.org</a></p>'] ].each do |text, html| - assert_equal html, format(text) + assert_equal html, to_html(text) end end def test_should_support_html_tables text = '<table style="background: red"><tr><td>Cell</td></tr></table>' - assert_equal '<table><tr><td>Cell</td></tr></table>', format(text) + assert_equal '<table><tr><td>Cell</td></tr></table>', to_html(text) end def test_should_remove_unsafe_uris @@ -263,7 +263,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase ['<img src="data:foobar">', '<img>'], ['<a href="javascript:bla">click me</a>', '<p><a>click me</a></p>'], ].each do |text, html| - assert_equal html, format(text) + assert_equal html, to_html(text) end end @@ -274,7 +274,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase %[sit<br/>amet <style>.foo { color: #fff; }</style> <script>alert("hello world");</script>] ] ].each do |expected, input| - assert_equal expected, format(input) + assert_equal expected, to_html(input) end end @@ -287,7 +287,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase expected = <<~EXPECTED <p>Task list:</p> - <ul class="task-list"> + <ul class="contains-task-list"> <li class="task-list-item"> <input type="checkbox" class="task-list-item-checkbox" disabled> Task 1 </li> @@ -296,7 +296,50 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase </ul> EXPECTED - assert_equal expected.gsub(%r{[\r\n\t]}, ''), format(text).gsub(%r{[\r\n\t]}, '').rstrip + assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '').rstrip + end + + def test_should_render_alert_blocks + text = <<~MD + > [!note] + > This is a note. + + > [!tip] + > This is a tip. + + > [!warning] + > This is a warning. + + > [!caution] + > This is a caution. + + > [!important] + > This is a important. + MD + + html = to_html(text) + %w[note tip warning caution important].each do |alert| + icon = Redmine::WikiFormatting::CommonMark::ALERT_TYPE_TO_ICON_NAME[alert] + # rubocop:disable Layout/LineLength + expected = %r{<div class="markdown-alert markdown-alert-#{alert}">\n<p class="markdown-alert-title"><svg class="s18 icon-svg" aria-hidden="true"><use href="/assets/icons-\w+.svg\#icon--#{icon}"></use></svg><span class="icon-label">#{alert.capitalize}</span></p>\n<p>This is a #{alert}.</p>\n</div>} + # rubocop:enable Layout/LineLength + assert_match expected, html + end + end + + def test_should_not_render_unknown_alert_type + text = <<~MD + > [!unknown] + > This should not become an alert. + MD + + html = to_html(text) + + assert_include "<blockquote>", html + assert_include "[!unknown]", html + assert_include "This should not become an alert.", html + + assert_not_include 'markdown-alert', html end private diff --git a/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb b/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb index 4c0282f2d..b2d19eab9 100644 --- a/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb +++ b/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb @@ -47,10 +47,14 @@ if Object.const_defined?(:Commonmarker) end def test_should_support_footnotes - input = %(<a href="#fn-1" id="fnref-1">foo</a>) - assert_equal input, filter(input) - input = %(<ol><li id="fn-1">footnote</li></ol>) - assert_equal input, filter(input) + [ + %(<a href="#fn-1" id="fnref-1">foo</a>), + %(<a href="#fn-1" id="fnref-1-2">foo</a>), + %(<ol><li id="fn-1">footnote</li></ol>), + ].each do |input| + assert_equal input, filter(input) + assert_equal input, filter(input) + end end def test_should_remove_invalid_ids @@ -71,6 +75,32 @@ if Object.const_defined?(:Commonmarker) assert_equal %(<code>foo</code>), filter(input) end + def test_should_allow_valid_alert_div_and_p_classes + html = <<~HTML + <div class="markdown-alert markdown-alert-tip"> + <p class="markdown-alert-title">Tip</p> + <p>Useful tip.</p> + </div> + HTML + + sanitized = filter(html) + + assert_include 'class="markdown-alert markdown-alert-tip"', sanitized + assert_include 'class="markdown-alert-title"', sanitized + end + + def test_should_remove_invalid_div_class + html = '<div class="bad-class">Text</div>' + sanitized = filter(html) + assert_not_includes 'bad-class', sanitized + end + + def test_should_remove_invalid_p_class + html = '<p class="bad-class">Text</p>' + sanitized = filter(html) + assert_not_include 'bad-class', sanitized + end + def test_should_allow_links_with_safe_url_schemes %w(http https ftp ssh foo).each do |scheme| input = %(<a href="#{scheme}://example.org/">foo</a>) diff --git a/test/unit/lib/redmine/wiki_formatting/html_sanitizer_test.rb b/test/unit/lib/redmine/wiki_formatting/html_sanitizer_test.rb index 11dddb5f8..f8793cf9f 100644 --- a/test/unit/lib/redmine/wiki_formatting/html_sanitizer_test.rb +++ b/test/unit/lib/redmine/wiki_formatting/html_sanitizer_test.rb @@ -35,4 +35,24 @@ class Redmine::WikiFormatting::HtmlSanitizerTest < ActiveSupport::TestCase input = %(<a href="javascript:alert('hello');">foo</a>) assert_equal "<a>foo</a>", @sanitizer.call(input) end + + def test_should_be_strict_with_task_list_items + to_test = { + %(<input type="checkbox" class="">) => "", + %(<input type="checkbox" class="task-list-item-checkbox other">) => "", + %(<input type="checkbox" class="task-list-item-checkbox" id="item1">) => %(<input type="checkbox" class="task-list-item-checkbox">), + %(<input type="text" class="">) => "", + %(<input />) => "", + %(<ul class="other"></ul) => "<ul></ul>", + %(<ul class="contains-task-list"></ul) => "<ul class=\"contains-task-list\"></ul>", + %(<ul class="contains-task-list" id="list1"></ul) => "<ul class=\"contains-task-list\"></ul>", + %(<li class="other"></li>) => "", + %(<li id="other"></li>) => "", + %(<li class="task-list-item"></li>) => "", + %(<li class="task-list-item">Item 1</li>) => "Item 1", + } + to_test.each do |input, result| + assert_equal result, @sanitizer.call(input) + end + end end diff --git a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb index 32280cfdf..678d4c6b2 100644 --- a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb +++ b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb @@ -466,19 +466,19 @@ class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase replacement = "New text" assert_equal( - [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"), + [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement) ) assert_equal( - [STR_WITHOUT_PRE[0..1], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), + [STR_WITHOUT_PRE[0..1], replacement, STR_WITHOUT_PRE[4]].join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(3, replacement) ) assert_equal( - [STR_WITHOUT_PRE[0..2], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), + [STR_WITHOUT_PRE[0..2], replacement, STR_WITHOUT_PRE[4]].join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(5, replacement) ) assert_equal( - [STR_WITHOUT_PRE[0..3], replacement].flatten.join("\n\n"), + [STR_WITHOUT_PRE[0..3], replacement].join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(6, replacement) ) assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(0, replacement) @@ -488,7 +488,7 @@ class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase def test_update_section_with_hash_should_update_the_requested_section replacement = "New text" assert_equal( - [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"), + [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE). update_section(2, replacement, ActiveSupport::Digest.hexdigest(STR_WITHOUT_PRE[1])) ) @@ -552,7 +552,7 @@ class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase text = STR_WITH_PRE.join("\n\n") replacement = "New text" assert_equal( - [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"), + [STR_WITH_PRE[0..1], "New text"].join("\n\n"), @formatter.new(text).update_section(3, replacement) ) end diff --git a/test/unit/member_test.rb b/test/unit/member_test.rb index f92841b76..df9088027 100644 --- a/test/unit/member_test.rb +++ b/test/unit/member_test.rb @@ -79,7 +79,7 @@ class MemberTest < ActiveSupport::TestCase [1, group_a_member.member_roles.find_by(role_id: 1).id], [1, group_b_member.member_roles.find_by(role_id: 1).id], [2, group_b_member.member_roles.find_by(role_id: 2).id], - ], test_user_member.member_roles.map{|r| [r.role_id, r.inherited_from]} + ].sort, test_user_member.member_roles.map{|r| [r.role_id, r.inherited_from]}.sort # Verify that a new non-inherited role is added and inherited roles are maintained test_user_member.set_editable_role_ids([3]) # Add Reporter role to test_user @@ -88,7 +88,7 @@ class MemberTest < ActiveSupport::TestCase [1, group_b_member.member_roles.find_by(role_id: 1).id], [2, group_b_member.member_roles.find_by(role_id: 2).id], [3, nil] - ], test_user_member.member_roles.map{|r| [r.role_id, r.inherited_from]} + ].sort, test_user_member.member_roles.map{|r| [r.role_id, r.inherited_from]}.sort end def test_validate @@ -108,7 +108,7 @@ class MemberTest < ActiveSupport::TestCase assert !member.save assert_include I18n.translate('activerecord.errors.messages.empty'), member.errors[:role] assert_equal 'Rôle doit être renseigné(e)', - [member.errors.full_messages].flatten.join + [member.errors.full_messages].join end def test_validate_member_role diff --git a/test/unit/principal_test.rb b/test/unit/principal_test.rb index 9eef5bbf3..61f775014 100644 --- a/test/unit/principal_test.rb +++ b/test/unit/principal_test.rb @@ -166,4 +166,8 @@ class PrincipalTest < ActiveSupport::TestCase r = Principal.like('vi_ci') assert_include user, r end + + def test_initials_should_return_nil + assert_nil Group.first.initials + end end diff --git a/test/unit/project_admin_query_test.rb b/test/unit/project_admin_query_test.rb index 421b1f58d..8e58e2efb 100644 --- a/test/unit/project_admin_query_test.rb +++ b/test/unit/project_admin_query_test.rb @@ -95,6 +95,7 @@ class ProjectAdminQueryTest < ActiveSupport::TestCase end def test_project_statuses_values_should_return_all_statuses + set_language_if_valid 'en' q = ProjectAdminQuery.new assert_equal [ ["active", "1"], diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb index ff2cef903..155a74b64 100644 --- a/test/unit/query_test.rb +++ b/test/unit/query_test.rb @@ -623,7 +623,7 @@ class QueryTest < ActiveSupport::TestCase query.add_filter('due_date', '><t+', ['15']) issues = find_issues_with_query(query) assert !issues.empty? - issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))} + issues.each {|issue| assert(issue.due_date.between?(Date.today, (Date.today + 15)))} end def test_operator_less_than_ago @@ -641,7 +641,7 @@ class QueryTest < ActiveSupport::TestCase query.add_filter('due_date', '><t-', ['3']) issues = find_issues_with_query(query) assert !issues.empty? - issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)} + issues.each {|issue| assert(issue.due_date.between?((Date.today - 3), Date.today))} end def test_operator_more_than_ago @@ -2326,7 +2326,7 @@ class QueryTest < ActiveSupport::TestCase values = issues.filter_map do |i| begin - Kernel.Float(i.custom_value_for(c.custom_field).to_s) + Kernel.Float(i.custom_value_for(c.custom_field).to_s, exception: false) rescue nil end diff --git a/test/unit/reaction_test.rb b/test/unit/reaction_test.rb new file mode 100644 index 000000000..9b3da0738 --- /dev/null +++ b/test/unit/reaction_test.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006- 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_relative '../test_helper' + +class ReactionTest < ActiveSupport::TestCase + test 'validates :inclusion of reactable_type' do + %w(Issue Journal News Comment Message).each do |type| + reaction = Reaction.new(reactable_type: type, user: User.new) + assert reaction.valid? + end + + assert_not Reaction.new(reactable_type: 'InvalidType', user: User.new).valid? + end + + test 'scope: by' do + user2_reactions = issues(:issues_001).reactions.by(users(:users_002)) + + assert_equal [reactions(:reaction_002)], user2_reactions + end + + test "should prevent duplicate reactions with unique constraint under concurrent creation" do + user = users(:users_001) + issue = issues(:issues_004) + + threads = [] + results = [] + + # Ensure both threads start at the same time + barrier = Concurrent::CyclicBarrier.new(2) + + # Create two threads to simulate concurrent creation + 2.times do + threads << Thread.new do + barrier.wait # Wait for both threads to be ready + begin + reaction = Reaction.create( + reactable: issue, + user: user + ) + results << reaction.persisted? + rescue ActiveRecord::RecordNotUnique + results << false + end + end + end + + # Wait for both threads to finish + threads.each(&:join) + + # Ensure only one reaction was created + assert_equal 1, Reaction.where(reactable: issue, user: user).count + assert_includes results, true + assert_equal 1, results.count(true) + end + + test 'build_detail_map_for generates a detail map for reactable objects' do + result = Reaction.build_detail_map_for([issues(:issues_001), issues(:issues_006)], users(:users_003)) + + expected = { + 1 => Reaction::Detail.new( + visible_users: [users(:users_003), users(:users_002), users(:users_001)], + user_reaction: reactions(:reaction_003) + ), + 6 => Reaction::Detail.new( + visible_users: [users(:users_002)], + user_reaction: nil + ) + } + assert_equal expected, result + + # When an object have no reactions, the result should be empty. + result = Reaction.build_detail_map_for([journals(:journals_002)], users(:users_002)) + + assert_empty result + end + + test 'build_detail_map_for filters users based on visibility' do + current_user = User.generate! + visible_user = users(:users_002) + non_visible_user = User.generate! + + project = Project.generate! + role = Role.generate!(users_visibility: 'members_of_visible_projects') + + User.add_to_project(current_user, project, role) + User.add_to_project(visible_user, project, roles(:roles_001)) + + issue = Issue.generate!(project: project) + + [current_user, visible_user, non_visible_user].each do |user| + issue.reactions.create!(user: user) + end + + result = Reaction.build_detail_map_for([issue], current_user) + + assert_equal( + [current_user, visible_user].sort_by(&:id), + result[issue.id].visible_users.sort_by(&:id) + ) + end +end diff --git a/test/unit/repository_bazaar_test.rb b/test/unit/repository_bazaar_test.rb index 23f3ce48f..5fec37973 100644 --- a/test/unit/repository_bazaar_test.rb +++ b/test/unit/repository_bazaar_test.rb @@ -50,6 +50,7 @@ class RepositoryBazaarTest < ActiveSupport::TestCase :log_encoding => 'UTF-8' ) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_blank_path_to_repository_error_message diff --git a/test/unit/repository_cvs_test.rb b/test/unit/repository_cvs_test.rb index af995eac0..84d0ed80b 100644 --- a/test/unit/repository_cvs_test.rb +++ b/test/unit/repository_cvs_test.rb @@ -36,6 +36,7 @@ class RepositoryCvsTest < ActiveSupport::TestCase :url => MODULE_NAME, :log_encoding => 'UTF-8') assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_blank_module_error_message diff --git a/test/unit/repository_git_test.rb b/test/unit/repository_git_test.rb index ec1ca5157..857be9442 100644 --- a/test/unit/repository_git_test.rb +++ b/test/unit/repository_git_test.rb @@ -41,6 +41,7 @@ class RepositoryGitTest < ActiveSupport::TestCase :path_encoding => 'ISO-8859-1' ) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_nondefault_repo_with_blank_identifier_destruction diff --git a/test/unit/repository_mercurial_test.rb b/test/unit/repository_mercurial_test.rb index 521bd06af..991d19a6d 100644 --- a/test/unit/repository_mercurial_test.rb +++ b/test/unit/repository_mercurial_test.rb @@ -35,6 +35,7 @@ class RepositoryMercurialTest < ActiveSupport::TestCase :path_encoding => 'ISO-8859-1' ) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_blank_path_to_repository_error_message diff --git a/test/unit/repository_subversion_test.rb b/test/unit/repository_subversion_test.rb index b4590ce31..dfdf520e7 100644 --- a/test/unit/repository_subversion_test.rb +++ b/test/unit/repository_subversion_test.rb @@ -30,6 +30,7 @@ class RepositorySubversionTest < ActiveSupport::TestCase @repository = Repository::Subversion.create(:project => @project, :url => self.class.subversion_repository_url) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_invalid_url diff --git a/test/unit/repository_test.rb b/test/unit/repository_test.rb index 53b5e0ee7..84c22a73f 100644 --- a/test/unit/repository_test.rb +++ b/test/unit/repository_test.rb @@ -455,7 +455,7 @@ class RepositoryTest < ActiveSupport::TestCase def test_stats_by_author_reflect_changesets_and_changes repository = Repository.find(10) - expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}} + expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}} assert_equal expected, repository.stats_by_author set = Changeset.create!( @@ -467,7 +467,7 @@ class RepositoryTest < ActiveSupport::TestCase ) Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file1') Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file2') - expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>5}} + expected = {"Dave Lopper"=>{:commits_count=>12, :changes_count=>5}} assert_equal expected, repository.stats_by_author end @@ -476,7 +476,7 @@ class RepositoryTest < ActiveSupport::TestCase # to ensure things are dynamically linked to Users User.find_by_login("dlopper").update_attribute(:firstname, "Dave's") repository = Repository.find(10) - expected = {"Dave's Lopper"=>{:commits_count=>10, :changes_count=>3}} + expected = {"Dave's Lopper"=>{:commits_count=>11, :changes_count=>3}} assert_equal expected, repository.stats_by_author end @@ -502,7 +502,7 @@ class RepositoryTest < ActiveSupport::TestCase # with committer="dlopper <dlopper@somefoo.net>" repository = Repository.find(10) - expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}} + expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}} assert_equal expected, repository.stats_by_author set = Changeset.create!( @@ -513,7 +513,7 @@ class RepositoryTest < ActiveSupport::TestCase :comments => 'Another commit by foo.' ) - expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}} + expected = {"Dave Lopper"=>{:commits_count=>12, :changes_count=>3}} assert_equal expected, repository.stats_by_author end diff --git a/test/unit/role_test.rb b/test/unit/role_test.rb index 21103919f..1d0d39d7e 100644 --- a/test/unit/role_test.rb +++ b/test/unit/role_test.rb @@ -175,6 +175,32 @@ class RoleTest < ActiveSupport::TestCase assert_equal false, role.permissions_tracker_ids?(:view_issues, 1) end + def test_allowed_to_with_symbol + role = Role.create!(:name => 'Test', :permissions => [:view_issues]) + assert_equal true, role.allowed_to?(:view_issues) + assert_equal false, role.allowed_to?(:add_issues) + end + + def test_allowed_to_with_symbol_and_scope + role = Role.create!(:name => 'Test', :permissions => [:view_issues, :delete_issues]) + assert_equal true, role.allowed_to?(:view_issues, [:view_issues, :add_issues]) + assert_equal false, role.allowed_to?(:add_issues, [:view_issues, :add_issues]) + assert_equal false, role.allowed_to?(:delete_issues, [:view_issues, :add_issues]) + end + + def test_allowed_to_with_hash + role = Role.create!(:name => 'Test', :permissions => [:view_issues]) + assert_equal true, role.allowed_to?(:controller => 'issues', :action => 'show') + assert_equal false, role.allowed_to?(:controller => 'issues', :action => 'create') + end + + def test_allowed_to_with_hash_and_scope + role = Role.create!(:name => 'Test', :permissions => [:view_issues, :delete_issues]) + assert_equal true, role.allowed_to?({:controller => 'issues', :action => 'show'}, [:view_issues, :add_issues]) + assert_equal false, role.allowed_to?({:controller => 'issues', :action => 'create'}, [:view_issues, :add_issues]) + assert_equal false, role.allowed_to?({:controller => 'issues', :action => 'destroy'}, [:view_issues, :add_issues]) + end + def test_has_permission_without_permissions role = Role.create!(:name => 'Test') assert_equal false, role.has_permission?(:delete_issues) diff --git a/test/unit/setting_test.rb b/test/unit/setting_test.rb index 4ae07cebb..cbfabbb02 100644 --- a/test/unit/setting_test.rb +++ b/test/unit/setting_test.rb @@ -147,4 +147,8 @@ class SettingTest < ActiveSupport::TestCase def test_default_text_formatting_for_new_installations_is_common_mark assert_equal 'common_mark', Setting.text_formatting end + + def test_default_wiki_tablesort_enabled_for_new_installations_is_disabled + assert_equal "0", Setting.wiki_tablesort_enabled + end end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index ede12e1ce..967771c87 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -589,6 +589,27 @@ class UserTest < ActiveSupport::TestCase end end + def test_initials_format + assert_equal 'JS', @jsmith.initials(:firstname_lastinitial) + assert_equal 'SJ', @jsmith.initials(:lastname_comma_firstname) + assert_equal 'SJ', @jsmith.initials(:lastname_firstname) + assert_equal 'JS', @jsmith.initials(:firstinitial_lastname) + assert_equal 'JL', User.new(:firstname => 'Jean-Philippe', :lastname => 'Lang').initials(:firstinitial_lastname) + assert_equal 'JS', @jsmith.initials(:undefined_format) + end + + def test_initials_should_use_setting_as_default_format + with_settings :user_format => :firstname_lastname do + assert_equal 'JS', @jsmith.reload.initials + end + with_settings :user_format => :username do + assert_equal 'JS', @jsmith.reload.initials + end + with_settings :user_format => :lastname do + assert_equal 'SM', @jsmith.reload.initials + end + end + def test_lastname_should_accept_255_characters u = User.first u.lastname = 'a' * 255 @@ -1376,4 +1397,77 @@ class UserTest < ActiveSupport::TestCase User.prune(7) end end + + def test_should_recognize_authorized_by_oauth + u = User.find 2 + assert_not u.authorized_by_oauth? + u.oauth_scope = [:add_issues, :view_issues] + assert u.authorized_by_oauth? + end + + def test_admin_should_be_limited_by_oauth_scope + u = User.find_by_admin(true) + assert u.admin? + + u.oauth_scope = [:add_issues, :view_issues] + assert_not u.admin? + + u.oauth_scope = [:add_issues, :view_issues, :admin] + assert u.admin? + + u = User.find_by_admin(false) + assert_not u.admin? + u.oauth_scope = [:add_issues, :view_issues, :admin] + assert_not u.admin? + end + + def test_oauth_scope_should_limit_global_user_permissions + admin = User.find 1 + user = User.find 2 + [admin, user].each do |u| + assert u.allowed_to?(:add_issues, nil, global: true) + assert u.allowed_to?(:view_issues, nil, global: true) + u.oauth_scope = [:view_issues] + assert_not u.allowed_to?(:add_issues, nil, global: true) + assert u.allowed_to?(:view_issues, nil, global: true) + end + end + + def test_oauth_scope_should_limit_project_user_permissions + admin = User.find 1 + project = Project.find 5 + assert admin.allowed_to?(:add_issues, project) + assert admin.allowed_to?(:view_issues, project) + admin.oauth_scope = [:view_issues] + assert_not admin.allowed_to?(:add_issues, project) + assert admin.allowed_to?(:view_issues, project) + + admin.oauth_scope = [:view_issues, :admin] + assert admin.allowed_to?(:add_issues, project) + assert admin.allowed_to?(:view_issues, project) + + user = User.find 2 + project = Project.find 1 + assert user.allowed_to?(:add_issues, project) + assert user.allowed_to?(:view_issues, project) + user.oauth_scope = [:view_issues] + assert_not user.allowed_to?(:add_issues, project) + assert user.allowed_to?(:view_issues, project) + + user.oauth_scope = [:view_issues, :admin] + assert_not user.allowed_to?(:add_issues, project) + assert user.allowed_to?(:view_issues, project) + end + + def test_destroy_should_delete_associated_reactions + users(:users_004).reactions.create!( + [ + {reactable: issues(:issues_001)}, + {reactable: issues(:issues_002)} + ] + ) + assert_difference 'Reaction.count', -2 do + users(:users_004).destroy + end + end end |