Project

General

Profile

Patch #44169 » 0001-Refactor-context-menus-controller-to-namespaced-sub-.patch

Marius BĂLTEANU, 2026-06-17 00:21

View differences:

app/controllers/context_menus/base_controller.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
module ContextMenus
21
  class BaseController < ApplicationController
22
    layout false
23
    helper :context_menus
24
    helper_method :url_for
25

  
26
    def url_for(options = nil)
27
      if options.is_a?(Hash) && options[:controller].present?
28
        controller_name = options[:controller].to_s
29
        unless controller_name.start_with?('/')
30
          options = options.dup
31
          options[:controller] = "/#{controller_name}"
32
        end
33
      end
34
      super
35
    end
36

  
37
    private
38

  
39
    def render_context_menu(template_name)
40
      @back = back_url
41
      render :template => "context_menus/#{template_name}"
42
    end
43
  end
44
end
app/controllers/context_menus/issues_controller.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
module ContextMenus
21
  class IssuesController < BaseController
22
    helper :watchers
23
    helper :issues
24

  
25
    before_action :find_issues, :only => :index
26

  
27
    def index
28
      issues
29
      render_context_menu 'issues'
30
    end
31

  
32
    private
33

  
34
    def issues
35
      if @issues.size == 1
36
        @issue = @issues.first
37
      end
38
      @issue_ids = @issues.map(&:id).sort
39

  
40
      @allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
41

  
42
      @can = {
43
        :edit => @issues.all?(&:attributes_editable?),
44
        :log_time => @issue&.time_loggable?,
45
        :copy => User.current.allowed_to?(:copy_issues, @projects) && Issue.allowed_target_projects.any?,
46
        :add_watchers => User.current.allowed_to?(:add_issue_watchers, @projects),
47
        :delete => @issues.all?(&:deletable?),
48
        :add_subtask => @issue && !@issue.closed? && User.current.allowed_to?(:manage_subtasks, @project)
49
      }
50

  
51
      @assignables = @issues.map(&:assignable_users).reduce(:&)
52
      @trackers = @projects.map {|p| Issue.allowed_target_trackers(p)}.reduce(:&)
53
      @versions = @projects.map {|p| p.shared_versions.open}.reduce(:&)
54

  
55
      @priorities = IssuePriority.active.reverse
56
      @back = back_url
57
      begin
58
        # Recognize the controller and action from the back_url to determine
59
        # which view triggered the context menu.
60
        if relative_url_root.present? && back_url&.starts_with?(relative_url_root)
61
          normalized_back_url = back_url.delete_prefix(relative_url_root)
62
        else
63
          normalized_back_url = back_url
64
        end
65
        route = Rails.application.routes.recognize_path(normalized_back_url)
66
        @include_delete =
67
          [
68
            {controller: 'issues', action: 'index'},
69
            {controller: 'gantts', action: 'show'},
70
            {controller: 'calendars', action: 'show'}
71
          ].any?(route.slice(:controller, :action))
72
      rescue ActionController::RoutingError
73
        @include_delete = false
74
      end
75

  
76
      @columns = params[:c]
77

  
78
      @options_by_custom_field = {}
79
      if @can[:edit]
80
        custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?).select {|field| field.format.bulk_edit_supported}
81
        custom_fields.each do |field|
82
          values = field.possible_values_options(@projects)
83
          if values.present?
84
            @options_by_custom_field[field] = values
85
          end
86
        end
87
      end
88

  
89
      @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
90
    end
91
  end
92
end
app/controllers/context_menus/projects_controller.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
module ContextMenus
21
  class ProjectsController < BaseController
22
    before_action :require_admin
23
    before_action :find_projects
24

  
25
    def index
26
      render_context_menu 'projects'
27
    end
28

  
29
    private
30

  
31
    def find_projects
32
      @projects = Project.where(id: params[:ids]).to_a
33
      if @projects.empty?
34
        render_404
35
        return
36
      end
37

  
38
      if @projects.size == 1
39
        @project = @projects.first
40
      end
41
    end
42
  end
43
end
app/controllers/context_menus/time_entries_controller.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
module ContextMenus
21
  class TimeEntriesController < BaseController
22
    before_action :find_time_entries
23

  
24
    def index
25
      @activities = @projects.map(&:activities).reduce(:&)
26

  
27
      edit_allowed = @time_entries.all? {|t| t.editable_by?(User.current)}
28
      @can = {:edit => edit_allowed, :delete => edit_allowed}
29
      @back = back_url
30

  
31
      @options_by_custom_field = {}
32
      if @can[:edit]
33
        custom_fields = @time_entries.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?).select {|field| field.format.bulk_edit_supported}
34
        custom_fields.each do |field|
35
          values = field.possible_values_options(@projects)
36
          if values.present?
37
            @options_by_custom_field[field] = values
38
          end
39
        end
40
      end
41

  
42
      render_context_menu 'time_entries'
43
    end
44

  
45
    private
46

  
47
    def find_time_entries
48
      @time_entries = TimeEntry.where(:id => params[:ids]).
49
        preload(:project => :time_entry_activities).
50
        preload(:user).to_a
51

  
52
      if @time_entries.blank? || !@time_entries.all?(&:visible?)
53
        render_404;
54
        return
55
      end
56

  
57
      if @time_entries.size == 1
58
        @time_entry = @time_entries.first
59
      end
60

  
61
      @projects = @time_entries.filter_map(&:project).uniq
62
      @project = @projects.first if @projects.size == 1
63
    end
64
  end
65
end
app/controllers/context_menus/users_controller.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
module ContextMenus
21
  class UsersController < BaseController
22
    before_action :require_admin
23
    before_action :find_users
24

  
25
    def index
26
      @groups = Group.givable.sorted.to_a
27
      @common_group_ids = Group.givable.joins(:groups_users).where(groups_users: { user_id: @users.map(&:id) }).distinct.pluck(:id).to_set
28

  
29
      render_context_menu 'users'
30
    end
31

  
32
    private
33

  
34
    def find_users
35
      @users = User.where(id: params[:id] || params[:ids]).to_a
36
      raise ActiveRecord::RecordNotFound if @users.empty?
37

  
38
      if @users.size == 1
39
        @user = @users.first
40
      end
41
    rescue ActiveRecord::RecordNotFound
42
      render_404
43
    end
44
  end
45
end
app/controllers/context_menus_controller.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
class ContextMenusController < ApplicationController
21
  helper :watchers
22
  helper :issues
23

  
24
  before_action :find_issues, :only => :issues
25
  before_action :require_admin, :only => [:projects, :users]
26

  
27
  def issues
28
    if @issues.size == 1
29
      @issue = @issues.first
30
    end
31
    @issue_ids = @issues.map(&:id).sort
32

  
33
    @allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
34

  
35
    @can = {
36
      :edit => @issues.all?(&:attributes_editable?),
37
      :log_time => @issue&.time_loggable?,
38
      :copy => User.current.allowed_to?(:copy_issues, @projects) && Issue.allowed_target_projects.any?,
39
      :add_watchers => User.current.allowed_to?(:add_issue_watchers, @projects),
40
      :delete => @issues.all?(&:deletable?),
41
      :add_subtask => @issue && !@issue.closed? && User.current.allowed_to?(:manage_subtasks, @project)
42
    }
43

  
44
    @assignables = @issues.map(&:assignable_users).reduce(:&)
45
    @trackers = @projects.map {|p| Issue.allowed_target_trackers(p)}.reduce(:&)
46
    @versions = @projects.map {|p| p.shared_versions.open}.reduce(:&)
47

  
48
    @priorities = IssuePriority.active.reverse
49
    @back = back_url
50
    begin
51
      # Recognize the controller and action from the back_url to determine
52
      # which view triggered the context menu.
53
      if relative_url_root.present? && back_url&.starts_with?(relative_url_root)
54
        normalized_back_url = back_url.delete_prefix(relative_url_root)
55
      else
56
        normalized_back_url = back_url
57
      end
58
      route = Rails.application.routes.recognize_path(normalized_back_url)
59
      @include_delete =
60
        [
61
          {controller: 'issues', action: 'index'},
62
          {controller: 'gantts', action: 'show'},
63
          {controller: 'calendars', action: 'show'}
64
        ].any?(route.slice(:controller, :action))
65
    rescue ActionController::RoutingError
66
      @include_delete = false
67
    end
68

  
69
    @columns = params[:c]
70

  
71
    @options_by_custom_field = {}
72
    if @can[:edit]
73
      custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?).select {|field| field.format.bulk_edit_supported}
74
      custom_fields.each do |field|
75
        values = field.possible_values_options(@projects)
76
        if values.present?
77
          @options_by_custom_field[field] = values
78
        end
79
      end
80
    end
81

  
82
    @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
83
    render :layout => false
84
  end
85

  
86
  def time_entries
87
    @time_entries = TimeEntry.where(:id => params[:ids]).
88
      preload(:project => :time_entry_activities).
89
      preload(:user).to_a
90

  
91
    if @time_entries.blank? || !@time_entries.all?(&:visible?)
92
      render_404;
93
      return
94
    end
95

  
96
    if @time_entries.size == 1
97
      @time_entry = @time_entries.first
98
    end
99

  
100
    @projects = @time_entries.filter_map(&:project).uniq
101
    @project = @projects.first if @projects.size == 1
102
    @activities = @projects.map(&:activities).reduce(:&)
103

  
104
    edit_allowed = @time_entries.all? {|t| t.editable_by?(User.current)}
105
    @can = {:edit => edit_allowed, :delete => edit_allowed}
106
    @back = back_url
107

  
108
    @options_by_custom_field = {}
109
    if @can[:edit]
110
      custom_fields = @time_entries.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?).select {|field| field.format.bulk_edit_supported}
111
      custom_fields.each do |field|
112
        values = field.possible_values_options(@projects)
113
        if values.present?
114
          @options_by_custom_field[field] = values
115
        end
116
      end
117
    end
118

  
119
    render :layout => false
120
  end
121

  
122
  def projects
123
    @projects = Project.where(id: params[:ids]).to_a
124
    if @projects.empty?
125
      render_404
126
      return
127
    end
128

  
129
    if @projects.size == 1
130
      @project = @projects.first
131
    end
132
    render layout: false
133
  end
134

  
135
  def users
136
    @users = User.where(id: params[:ids]).to_a
137

  
138
    (render_404; return) unless @users.present?
139
    if @users.size == 1
140
      @user = @users.first
141
    end
142

  
143
    @groups = Group.givable.sorted.to_a
144
    @common_group_ids = Group.givable.joins(:groups_users).where(groups_users: { user_id: @users.map(&:id) }).distinct.pluck(:id).to_set
145
    @back = back_url
146

  
147
    render layout: false
148
  end
149
end
config/routes.rb
58 58
  match '/wiki_pages/auto_complete', :to => 'auto_completes#wiki_pages', :via => :get, :as => 'auto_complete_wiki_pages'
59 59

  
60 60
  # Misc issue routes. TODO: move into resources
61
  match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post]
61
  match '/issues/context_menu', :to => 'context_menus/issues#index', :as => 'issues_context_menu', :via => [:get, :post]
62 62
  match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get
63 63
  match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
64 64

  
......
117 117
  match 'my/twofa/backup_codes', :controller => 'twofa_backup_codes', :action => 'show', :via => [:get]
118 118
  match 'users/:user_id/twofa/deactivate', :controller => 'twofa', :action => 'admin_deactivate', :via => :post
119 119

  
120
  match '/users/context_menu', to: 'context_menus#users', as: :users_context_menu, via: [:get, :post]
120
  match '/users/context_menu', to: 'context_menus/users#index', as: :users_context_menu, via: [:get, :post]
121 121
  resources :users do
122 122
    collection do
123 123
      delete 'bulk_destroy'
......
258 258
    post 'add_attachment', :on => :member
259 259
  end
260 260

  
261
  match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post]
261
  match '/time_entries/context_menu', :to => 'context_menus/time_entries#index', :as => :time_entries_context_menu, :via => [:get, :post]
262 262

  
263 263
  resources :time_entries, :controller => 'timelog', :except => :destroy do
264 264
    member do
......
384 384
  post 'admin/test_email', :to => 'admin#test_email', :as => 'test_email'
385 385
  post 'admin/default_configuration', :to => 'admin#default_configuration'
386 386

  
387
  match '/admin/projects_context_menu', :to => 'context_menus#projects', :as => 'projects_context_menu', :via => [:get, :post]
387
  match '/admin/projects_context_menu', :to => 'context_menus/projects#index', :as => 'projects_context_menu', :via => [:get, :post]
388 388

  
389 389
  resources :auth_sources do
390 390
    member do
test/functional/context_menus/issues_controller.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
require_relative '../../test_helper'
21

  
22
module ContextMenus
23
  class IssuesControllerTest < Redmine::ControllerTest
24
    def test_context_menu_one_issue_should_link_to_issue_path
25
      @request.session[:user_id] = 2
26
      get(
27
        :index,
28
        :params => {
29
          :ids => [1],
30
          :back_url => '/issues'
31
        }
32
      )
33
      assert_response :success
34

  
35
      assert_select 'a.icon-edit[href=?]', '/issues/1/edit', :text => 'Edit'
36
      assert_select 'a.icon-copy-link[data-clipboard-text=?]', 'http://test.host/issues/1', :text => 'Copy link'
37
      assert_select 'a.icon-copy[href=?]', '/projects/ecookbook/issues/1/copy', :text => 'Copy'
38
      assert_select 'a.icon-del[href*=?]', 'ids%5B%5D=1', :text => 'Delete issue'
39

  
40
      # Statuses
41
      assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bstatus_id%5D=5', :text => 'Closed'
42
      assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bpriority_id%5D=8', :text => 'Immediate'
43
      # No inactive priorities
44
      assert_select 'a', :text => /Inactive Priority/, :count => 0
45
      # Versions
46
      assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bfixed_version_id%5D=3', :text => '2.0'
47
      assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bfixed_version_id%5D=4', :text => 'eCookbook Subproject 1 - 2.0'
48
      # Assignees
49
      assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper'
50
    end
51

  
52
    def test_context_menu_multiple_issues_should_link_to_bulk_update_issues_path
53
      @request.session[:user_id] = 2
54
      get :index, :params => {
55
        :ids => [1, 2],
56
        :back_url => '/projects/ecookbook/issues'
57
      }
58
      assert_response :success
59

  
60
      assert_select 'a.icon-edit[href=?]', '/issues/bulk_edit?ids%5B%5D=1&ids%5B%5D=2', :text => 'Bulk edit'
61
      assert_select 'a.icon-copy[href=?]', '/issues/bulk_edit?copy=1&ids%5B%5D=1&ids%5B%5D=2', :text => 'Copy'
62
      assert_select 'a.icon-del[href*=?]', 'ids%5B%5D=1&ids%5B%5D=2', :text => 'Delete issues'
63

  
64
      # Statuses
65
      assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bstatus_id%5D=5', :text => 'Closed'
66
      assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bpriority_id%5D=8', :text => 'Immediate'
67
      # No inactive priorities
68
      assert_select 'a', :text => /Inactive Priority/, :count => 0
69
      # Versions
70
      assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bfixed_version_id%5D=3', :text => '2.0'
71
      assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bfixed_version_id%5D=4', :text => 'eCookbook Subproject 1 - 2.0'
72
      # Assignees
73
      assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper'
74
    end
75

  
76
    def test_context_menu_one_issue_by_anonymous
77
      with_settings :default_language => 'en' do
78
        get(
79
          :index,
80
          :params => {
81
            :ids => [1],
82
            :back_url => '/issues'
83
          }
84
        )
85
        assert_response :success
86

  
87
        assert_select 'a.icon-del.disabled[href="#"]', :text => 'Delete issue'
88
      end
89
    end
90

  
91
    def test_context_menu_multiple_issues_of_same_project
92
      @request.session[:user_id] = 2
93
      get(
94
        :index,
95
        :params => {
96
          :ids => [1, 2],
97
          :back_url => '/issues'
98
        }
99
      )
100
      assert_response :success
101

  
102
      ids = [1, 2].map {|i| "ids%5B%5D=#{i}"}.join('&')
103

  
104
      assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Bulk edit'
105
      # issue_id: '1,2', set_filter: 1, status_id: '*'
106
      assert_select 'a.icon-copy-link[data-clipboard-text=?]', "http://test.host/projects/ecookbook/issues?issue_id=1%2C2&set_filter=1&status_id=%2A", :text => 'Copy link'
107
      assert_select 'a.icon-copy[href=?]', "/issues/bulk_edit?copy=1&#{ids}", :text => 'Copy'
108
      assert_select 'a.icon-del[href*=?]', ids, :text => 'Delete issues'
109

  
110
      assert_select 'a[href*=?]', 'issue%5Bstatus_id%5D=5', :text => 'Closed'
111
      assert_select 'a[href*=?]', 'issue%5Bpriority_id%5D=8', :text => 'Immediate'
112
      assert_select 'a[href*=?]', 'issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper'
113
    end
114

  
115
    def test_context_menu_multiple_issues_of_different_projects
116
      @request.session[:user_id] = 2
117
      get(
118
        :index,
119
        :params => {
120
          :ids => [1, 2, 6],
121
          :back_url => '/issues'
122
        }
123
      )
124
      assert_response :success
125

  
126
      ids = [1, 2, 6].map {|i| "ids%5B%5D=#{i}"}.join('&')
127

  
128
      assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Bulk edit'
129
      # issue_id: '1,2,6', set_filter: 1, status_id: '*'
130
      assert_select 'a.icon-copy-link[data-clipboard-text=?]', "http://test.host/issues?issue_id=1%2C2%2C6&set_filter=1&status_id=%2A", :text => 'Copy link'
131
      assert_select 'a.icon-del[href*=?]', ids, :text => 'Delete issues'
132

  
133
      assert_select 'a[href*=?]', 'issue%5Bstatus_id%5D=5', :text => 'Closed'
134
      assert_select 'a[href*=?]', 'issue%5Bpriority_id%5D=8', :text => 'Immediate'
135
      assert_select 'a[href*=?]', 'issue%5Bassigned_to_id%5D=2', :text => 'John Smith'
136
    end
137

  
138
    def test_context_menu_should_include_list_custom_fields
139
      field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
140
        :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
141
      @request.session[:user_id] = 2
142
      get(
143
        :index,
144
        :params => {
145
          :ids => [1]
146
        }
147
      )
148
      assert_select "li.cf_#{field.id}" do
149
        assert_select 'a[href="#"]', :text => 'List'
150
        assert_select 'ul' do
151
          assert_select 'a', 3
152
          assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo", :text => 'Foo'
153
          assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
154
        end
155
      end
156
    end
157

  
158
    def test_context_menu_multiple_issues_should_include_list_custom_fields
159
      field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
160
        :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
161
      @request.session[:user_id] = 2
162
      get(
163
        :index,
164
        :params => {
165
          :ids => [1, 2]
166
        }
167
      )
168
      assert_select "li.cf_#{field.id}" do
169
        assert_select 'a[href="#"]', :text => 'List'
170
        assert_select 'ul' do
171
          assert_select 'a', 3
172
          assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&ids%5B%5D=2&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo", :text => 'Foo'
173
          assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&ids%5B%5D=2&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
174
        end
175
      end
176
    end
177

  
178
    def test_context_menu_should_not_include_null_value_for_required_custom_fields
179
      field = IssueCustomField.create!(:name => 'List', :is_required => true, :field_format => 'list',
180
        :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
181
      @request.session[:user_id] = 2
182
      get(
183
        :index,
184
        :params => {
185
          :ids => [1, 2]
186
        }
187
      )
188
      assert_select "li.cf_#{field.id}" do
189
        assert_select 'a[href="#"]', :text => 'List'
190
        assert_select 'ul' do
191
          assert_select 'a', 2
192
          assert_select 'a', :text => 'none', :count => 0
193
        end
194
      end
195
    end
196

  
197
    def test_context_menu_on_single_issue_should_select_current_custom_field_value
198
      field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
199
        :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
200
      issue = Issue.find(1)
201
      issue.custom_field_values = {field.id => 'Bar'}
202
      issue.save!
203
      @request.session[:user_id] = 2
204
      get(
205
        :index,
206
        :params => {
207
          :ids => [1]
208
        }
209
      )
210
      assert_select "li.cf_#{field.id}" do
211
        assert_select 'a[href="#"]', :text => 'List'
212
        assert_select 'ul' do
213
          assert_select 'a', 3
214
          assert_select 'a.icon', :text => 'Bar'
215
        end
216
      end
217
    end
218

  
219
    def test_context_menu_should_include_bool_custom_fields
220
      field = IssueCustomField.create!(:name => 'Bool', :field_format => 'bool',
221
        :is_for_all => true, :tracker_ids => [1, 2, 3])
222
      @request.session[:user_id] = 2
223
      get(
224
        :index,
225
        :params => {
226
          :ids => [1]
227
        }
228
      )
229
      assert_select "li.cf_#{field.id}" do
230
        assert_select 'a[href="#"]', :text => 'Bool'
231
        assert_select 'ul' do
232
          assert_select 'a', 3
233
          assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=0", :text => 'No'
234
          assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=1", :text => 'Yes'
235
          assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
236
        end
237
      end
238
    end
239

  
240
    def test_context_menu_should_include_user_custom_fields
241
      field = IssueCustomField.create!(:name => 'User', :field_format => 'user',
242
        :is_for_all => true, :tracker_ids => [1, 2, 3])
243
      @request.session[:user_id] = 2
244
      get(
245
        :index,
246
        :params => {
247
          :ids => [1]
248
        }
249
      )
250
      assert_select "li.cf_#{field.id}" do
251
        assert_select 'a[href="#"]', :text => 'User'
252
        assert_select 'ul' do
253
          assert_select 'a', Project.find(1).members.count + 2 # users + 'none' + 'me'
254
          assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=2", :text => 'John Smith'
255
          assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
256
        end
257
      end
258
    end
259

  
260
    def test_context_menu_should_include_version_custom_fields
261
      field = IssueCustomField.create!(:name => 'Version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1, 2, 3])
262
      @request.session[:user_id] = 2
263
      get(
264
        :index,
265
        :params => {
266
          :ids => [1]
267
        }
268
      )
269
      assert_select "li.cf_#{field.id}" do
270
        assert_select 'a[href="#"]', :text => 'Version'
271
        assert_select 'ul' do
272
          assert_select 'a', Project.find(1).shared_versions.count + 1
273
          assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=3", :text => '2.0'
274
          assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
275
        end
276
      end
277
    end
278

  
279
    def test_context_menu_should_show_enabled_custom_fields_for_the_role_only
280
      enabled_cf =
281
        IssueCustomField.generate!(
282
          :field_format => 'bool', :is_for_all => true,
283
          :tracker_ids => [1], :visible => false, :role_ids => [1, 2]
284
        )
285
      disabled_cf =
286
        IssueCustomField.generate!(
287
          :field_format => 'bool', :is_for_all => true,
288
          :tracker_ids => [1], :visible => false, :role_ids => [2]
289
        )
290
      issue = Issue.generate!(:project_id => 1, :tracker_id => 1)
291

  
292
      @request.session[:user_id] = 2
293
      get(
294
        :index,
295
        :params => {
296
          :ids => [issue.id]
297
        }
298
      )
299
      assert_select "li.cf_#{enabled_cf.id}"
300
      assert_select "li.cf_#{disabled_cf.id}", 0
301
    end
302

  
303
    def test_context_menu_by_assignable_user_should_include_assigned_to_me_link
304
      @request.session[:user_id] = 2
305
      get(
306
        :index,
307
        :params => {
308
          :ids => [1, 2]
309
        }
310
      )
311
      assert_response :success
312

  
313
      assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&ids%5B%5D=2&issue%5Bassigned_to_id%5D=2', :text => / me /
314
    end
315

  
316
    def test_context_menu_should_propose_shared_versions_for_issues_from_different_projects
317
      @request.session[:user_id] = 2
318
      version = Version.create!(:name => 'Shared', :sharing => 'system', :project_id => 1)
319

  
320
      get(
321
        :index,
322
        :params => {
323
          :ids => [1, 4]
324
        }
325
      )
326
      assert_response :success
327

  
328
      assert_select 'a', :text => 'eCookbook - Shared'
329
    end
330

  
331
    def test_context_menu_should_respect_five_percent_increments
332
      with_settings :issue_done_ratio => 'issue_field', :issue_done_ratio_interval => 5 do
333
        @request.session[:user_id] = 2
334
        get(
335
          :index,
336
          :params => {
337
            :ids => [1, 2]
338
          }
339
        )
340
        assert_response :success
341

  
342
        assert_select 'a[href*=?]', '/issues/bulk_update?ids%5B%5D=1&ids%5B%5D=2&issue%5Bdone_ratio%5D=0', :text => '0%'
343
        assert_select 'a[href*=?]', '/issues/bulk_update?ids%5B%5D=1&ids%5B%5D=2&issue%5Bdone_ratio%5D=5', :text => '5%'
344
        assert_select 'a[href*=?]', '/issues/bulk_update?ids%5B%5D=1&ids%5B%5D=2&issue%5Bdone_ratio%5D=10', :text => '10%'
345
        assert_select 'a[href*=?]', '/issues/bulk_update?ids%5B%5D=1&ids%5B%5D=2&issue%5Bdone_ratio%5D=55', :text => '55%'
346
        assert_select 'a[href*=?]', '/issues/bulk_update?ids%5B%5D=1&ids%5B%5D=2&issue%5Bdone_ratio%5D=100', :text => '100%'
347
      end
348
    end
349

  
350
    def test_context_menu_should_include_add_subtask_link
351
      @request.session[:user_id] = 2
352
      get(
353
        :index,
354
        :params => {
355
          :ids => [1]
356
        }
357
      )
358
      assert_response :success
359

  
360
      assert_select 'a.icon-add[href=?]', '/projects/ecookbook/issues/new?issue%5Bparent_issue_id%5D=1&issue%5Btracker_id%5D=1', :text => 'Add subtask'
361
    end
362

  
363
    def test_context_menu_with_closed_issue_should_not_include_add_subtask_link
364
      @request.session[:user_id] = 2
365
      get(
366
        :index,
367
        :params => {
368
          :ids => [8]
369
        }
370
      )
371
      assert_response :success
372

  
373
      assert_select 'a.icon-add', :text => 'Add subtask', :count => 0
374
    end
375

  
376
    def test_context_menu_multiple_issues_should_not_include_add_subtask_link
377
      @request.session[:user_id] = 2
378
      get(
379
        :index,
380
        :params => {
381
          :ids => [1, 2]
382
        }
383
      )
384
      assert_response :success
385

  
386
      assert_select 'a.icon-add', :text => 'Add subtask', :count => 0
387
    end
388

  
389
    def test_context_menu_with_issue_that_is_not_visible_should_fail
390
      get(
391
        :index,
392
        :params => {
393
          :ids => [1, 4] # issue 4 is not visible
394
        }
395
      )
396
      assert_response :found
397
    end
398

  
399
    def test_should_respond_with_404_without_ids
400
      get :index
401
      assert_response :not_found
402
    end
403

  
404
    def test_context_menu_should_include_delete_for_allowed_back_urls
405
      @request.session[:user_id] = 2
406
      %w[
407
        /issues
408
        /projects/ecookbook/issues/gantt
409
        /projects/ecookbook/issues/calendar
410
      ].each do |back_url|
411
        get :index, :params => { :ids => [1], :back_url => back_url }
412
        assert_response :success
413
        assert_select 'a.icon-del', :text => /Delete/
414
      end
415
    end
416

  
417
    def test_context_menu_with_suburi_should_include_delete_for_allowed_back_urls
418
      @relative_url_root = Redmine::Utils.relative_url_root
419
      Redmine::Utils.relative_url_root = '/redmine'
420

  
421
      @request.session[:user_id] = 2
422
      %w[
423
        /redmine/issues
424
        /redmine/projects/ecookbook/issues/gantt
425
        /redmine/projects/ecookbook/issues/calendar
426
      ].each do |back_url|
427
        get :index, :params => { :ids => [1], :back_url => back_url }
428
        assert_response :success
429
        assert_select 'a.icon-del', :text => /Delete/
430
      end
431
    ensure
432
      Redmine::Utils.relative_url_root = @relative_url_root
433
    end
434

  
435
    def test_context_menu_should_not_include_delete_for_disallowed_back_urls
436
      @request.session[:user_id] = 2
437
      %w[
438
        /issues/1
439
        /projects/ecookbook/roadmap
440
        /not/a/real/path
441
      ].each do |back_url|
442
        get :index, :params => { :ids => [1], :back_url => back_url }
443
        assert_response :success
444
        assert_select 'a.icon-del', :count => 0
445
      end
446
    end
447
  end
448
end
test/functional/context_menus/projects_controller.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
require_relative '../../test_helper'
21

  
22
module ContextMenus
23
  class ProjectsControllerTest < Redmine::ControllerTest
24
    def test_index_admin_user
25
      @request.session[:user_id] = 1
26

  
27
      get(
28
        :index,
29
        :params => {
30
          :ids => [1, 2]
31
        }
32
      )
33

  
34
      assert_response :success
35
    end
36

  
37
    def test_index_not_admin_user
38
      @request.session[:user_id] = 2
39

  
40
      get(
41
        :index,
42
        :params => {
43
          :ids => [1, 2]
44
        }
45
      )
46

  
47
      assert_response :forbidden
48
    end
49
  end
50
end
test/functional/context_menus/time_entries_controller.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
require_relative '../../test_helper'
21

  
22
module ContextMenus
23
  class TimeEntriesControllerTest < Redmine::ControllerTest
24
    def test_context_menu_for_one_time_entry
25
      @request.session[:user_id] = 2
26
      get(
27
        :index,
28
        :params => {
29
          :ids => [1]
30
        }
31
      )
32
      assert_response :success
33

  
34
      assert_select 'a:not(.disabled)', :text => 'Edit'
35
    end
36

  
37
    def test_time_entries_context_menu
38
      @request.session[:user_id] = 2
39
      get(
40
        :index,
41
        :params => {
42
          :ids => [1, 2]
43
        }
44
      )
45
      assert_response :success
46

  
47
      assert_select 'a:not(.disabled)', :text => 'Bulk edit'
48
    end
49

  
50
    def test_time_entries_context_menu_should_include_custom_fields
51
      field = TimeEntryCustomField.generate!(:name => "Field", :field_format => "list", :possible_values => ["foo", "bar"])
52

  
53
      @request.session[:user_id] = 2
54
      get(
55
        :index,
56
        :params => {
57
          :ids => [1, 2]
58
        }
59
      )
60
      assert_response :success
61

  
62
      assert_select "li.cf_#{field.id}" do
63
        assert_select 'a[href="#"]', :text => "Field"
64
        assert_select 'ul' do
65
          assert_select 'a', 3
66
          assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=foo", :text => 'foo'
67
          assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=bar", :text => 'bar'
68
          assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
69
        end
70
      end
71
    end
72

  
73
    def test_time_entries_context_menu_with_time_entry_that_is_not_visible_should_fail
74
      project = Project.find(2)
75
      project.enable_module!(:time_tracking)
76
      time_entry = TimeEntry.generate!(project: project)
77

  
78
      @request.session[:user_id] = 2
79

  
80
      get(
81
        :index,
82
        :params => {
83
          :ids => [1, 5, time_entry.id]
84
        }
85
      )
86

  
87
      assert_response :not_found
88
    end
89

  
90
    def test_time_entries_context_menu_with_edit_own_time_entries_permission
91
      @request.session[:user_id] = 2
92
      Role.find_by_name('Manager').remove_permission! :edit_time_entries
93
      Role.find_by_name('Manager').add_permission! :edit_own_time_entries
94
      ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
95
      get(
96
        :index,
97
        :params => {
98
          :ids => ids
99
        }
100
      )
101
      assert_response :success
102

  
103
      assert_select 'a:not(.disabled)', :text => 'Bulk edit'
104
    end
105

  
106
    def test_time_entries_context_menu_without_edit_permission
107
      @request.session[:user_id] = 2
108
      Role.find_by_name('Manager').remove_permission! :edit_time_entries
109
      get(
110
        :index,
111
        :params => {
112
          :ids => [1, 2]
113
        }
114
      )
115
      assert_response :success
116

  
117
      assert_select 'a.disabled', :text => 'Bulk edit'
118
    end
119
  end
120
end
test/functional/context_menus/users_controller.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
require_relative '../../test_helper'
21

  
22
module ContextMenus
23
  class UsersControllerTest < Redmine::ControllerTest
24
    def test_users_context_menu
25
      @request.session[:user_id] = 1 # admin
26
      get :index, :params => {:ids => [8]}
27
      assert_response :success
28

  
29
      assert_select 'li.folder' do
30
        assert_select 'a', :text => 'Add to group'
31
        assert_select 'ul' do
32
          assert_select 'a', :text => 'A Team'
33
        end
34
      end
35
      # User 8 is in Group 10
36
      assert_select 'li.folder' do
37
        assert_select 'a', :text => 'Remove from group'
38
        assert_select 'a', :text => 'A Team'
39
      end
40
    end
41

  
42
    def test_users_context_menu_bulk
43
      @request.session[:user_id] = 1 # admin
44
      # Add user 2 to group 10 (user 8 is already there)
45
      Group.find(10).users << User.find(2)
46

  
47
      get :index, :params => {:ids => [2, 8]}
48
      assert_response :success
49

  
50
      assert_select 'li.folder' do
51
        assert_select 'a', :text => 'Add to group'
52
        assert_select 'ul' do
53
          assert_select 'a', :text => 'A Team'
54
          assert_select 'a', :text => 'B Team'
55
        end
56
      end
57
      # Both users are in Group 10
58
      assert_select 'li.folder' do
59
        assert_select 'a', :text => 'Remove from group'
60
        assert_select 'a', :text => 'A Team'
61
      end
62
    end
63

  
64
    def test_users_context_menu_bulk_with_different_groups
65
      @request.session[:user_id] = 1 # admin
66
      # User 8 is in Group 10
67
      # Add User 2 to Group 11
68
      Group.find(11).users << User.find(2)
69

  
70
      get :index, :params => {:ids => [2, 8]}
71
      assert_response :success
72

  
73
      # Both Group 10 and Group 11 should be in the Remove submenu
74
      assert_select 'li.folder' do
75
        assert_select 'a', :text => 'Remove from group'
76
        assert_select 'ul' do
77
          assert_select 'a', :text => 'A Team'
78
          assert_select 'a', :text => 'B Team'
79
        end
80
      end
81
    end
82

  
83
    def test_users_context_menu_without_permission
84
      @request.session[:user_id] = 2
85

  
86
      get :index, :params => {:ids => [8]}
87
      assert_response :forbidden
88
    end
89
  end
90
end
test/functional/context_menus_controller_test.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
require_relative '../test_helper'
21

  
22
class ContextMenusControllerTest < Redmine::ControllerTest
23
  def test_context_menu_one_issue_should_link_to_issue_path
24
    @request.session[:user_id] = 2
25
    get(
26
      :issues,
27
      :params => {
28
        :ids => [1],
29
        :back_url => '/issues'
30
      }
31
    )
32
    assert_response :success
33

  
34
    assert_select 'a.icon-edit[href=?]', '/issues/1/edit', :text => 'Edit'
35
    assert_select 'a.icon-copy-link[data-clipboard-text=?]', 'http://test.host/issues/1', :text => 'Copy link'
36
    assert_select 'a.icon-copy[href=?]', '/projects/ecookbook/issues/1/copy', :text => 'Copy'
37
    assert_select 'a.icon-del[href*=?]', 'ids%5B%5D=1', :text => 'Delete issue'
38

  
39
    # Statuses
40
    assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bstatus_id%5D=5', :text => 'Closed'
41
    assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bpriority_id%5D=8', :text => 'Immediate'
42
    # No inactive priorities
43
    assert_select 'a', :text => /Inactive Priority/, :count => 0
44
    # Versions
45
    assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bfixed_version_id%5D=3', :text => '2.0'
46
    assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bfixed_version_id%5D=4', :text => 'eCookbook Subproject 1 - 2.0'
47
    # Assignees
48
    assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper'
49
  end
50

  
51
  def test_context_menu_multiple_issues_should_link_to_bulk_update_issues_path
52
    @request.session[:user_id] = 2
53
    get :issues, :params => {
54
      :ids => [1, 2],
55
      :back_url => '/projects/ecookbook/issues'
56
    }
57
    assert_response :success
58

  
59
    assert_select 'a.icon-edit[href=?]', '/issues/bulk_edit?ids%5B%5D=1&ids%5B%5D=2', :text => 'Bulk edit'
60
    assert_select 'a.icon-copy[href=?]', '/issues/bulk_edit?copy=1&ids%5B%5D=1&ids%5B%5D=2', :text => 'Copy'
61
    assert_select 'a.icon-del[href*=?]', 'ids%5B%5D=1&ids%5B%5D=2', :text => 'Delete issues'
62

  
63
    # Statuses
64
    assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bstatus_id%5D=5', :text => 'Closed'
65
    assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bpriority_id%5D=8', :text => 'Immediate'
66
    # No inactive priorities
67
    assert_select 'a', :text => /Inactive Priority/, :count => 0
68
    # Versions
69
    assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bfixed_version_id%5D=3', :text => '2.0'
70
    assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bfixed_version_id%5D=4', :text => 'eCookbook Subproject 1 - 2.0'
71
    # Assignees
72
    assert_select 'a[href*=?][data-method="patch"]', 'issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper'
73
  end
74

  
75
  def test_context_menu_one_issue_by_anonymous
76
    with_settings :default_language => 'en' do
77
      get(
78
        :issues,
79
        :params => {
80
          :ids => [1],
81
          :back_url => '/issues'
82
        }
83
      )
84
      assert_response :success
85

  
86
      assert_select 'a.icon-del.disabled[href="#"]', :text => 'Delete issue'
87
    end
88
  end
89

  
90
  def test_context_menu_multiple_issues_of_same_project
91
    @request.session[:user_id] = 2
92
    get(
93
      :issues,
94
      :params => {
95
        :ids => [1, 2],
96
        :back_url => '/issues'
97
      }
98
    )
99
    assert_response :success
100

  
101
    ids = [1, 2].map {|i| "ids%5B%5D=#{i}"}.join('&')
102

  
103
    assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Bulk edit'
104
    # issue_id: '1,2', set_filter: 1, status_id: '*'
105
    assert_select 'a.icon-copy-link[data-clipboard-text=?]', "http://test.host/projects/ecookbook/issues?issue_id=1%2C2&set_filter=1&status_id=%2A", :text => 'Copy link'
106
    assert_select 'a.icon-copy[href=?]', "/issues/bulk_edit?copy=1&#{ids}", :text => 'Copy'
107
    assert_select 'a.icon-del[href*=?]', ids, :text => 'Delete issues'
108

  
109
    assert_select 'a[href*=?]', 'issue%5Bstatus_id%5D=5', :text => 'Closed'
110
    assert_select 'a[href*=?]', 'issue%5Bpriority_id%5D=8', :text => 'Immediate'
111
    assert_select 'a[href*=?]', 'issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper'
112
  end
113

  
114
  def test_context_menu_multiple_issues_of_different_projects
115
    @request.session[:user_id] = 2
116
    get(
117
      :issues,
118
      :params => {
119
        :ids => [1, 2, 6],
120
        :back_url => '/issues'
121
      }
122
    )
123
    assert_response :success
124

  
125
    ids = [1, 2, 6].map {|i| "ids%5B%5D=#{i}"}.join('&')
126

  
127
    assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Bulk edit'
128
    # issue_id: '1,2,6', set_filter: 1, status_id: '*'
129
    assert_select 'a.icon-copy-link[data-clipboard-text=?]', "http://test.host/issues?issue_id=1%2C2%2C6&set_filter=1&status_id=%2A", :text => 'Copy link'
130
    assert_select 'a.icon-del[href*=?]', ids, :text => 'Delete issues'
131

  
132
    assert_select 'a[href*=?]', 'issue%5Bstatus_id%5D=5', :text => 'Closed'
133
    assert_select 'a[href*=?]', 'issue%5Bpriority_id%5D=8', :text => 'Immediate'
134
    assert_select 'a[href*=?]', 'issue%5Bassigned_to_id%5D=2', :text => 'John Smith'
135
  end
136

  
137
  def test_context_menu_should_include_list_custom_fields
138
    field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
139
      :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
140
    @request.session[:user_id] = 2
141
    get(
142
      :issues,
143
      :params => {
144
        :ids => [1]
145
      }
146
    )
147
    assert_select "li.cf_#{field.id}" do
148
      assert_select 'a[href="#"]', :text => 'List'
149
      assert_select 'ul' do
150
        assert_select 'a', 3
151
        assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo", :text => 'Foo'
152
        assert_select 'a[href=?]', "/issues/1?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
153
      end
154
    end
155
  end
156

  
157
  def test_context_menu_multiple_issues_should_include_list_custom_fields
158
    field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
159
      :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
160
    @request.session[:user_id] = 2
161
    get(
162
      :issues,
163
      :params => {
164
        :ids => [1, 2]
165
      }
166
    )
167
    assert_select "li.cf_#{field.id}" do
168
      assert_select 'a[href="#"]', :text => 'List'
169
      assert_select 'ul' do
170
        assert_select 'a', 3
171
        assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&ids%5B%5D=2&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo", :text => 'Foo'
172
        assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&ids%5B%5D=2&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
173
      end
174
    end
175
  end
176

  
177
  def test_context_menu_should_not_include_null_value_for_required_custom_fields
178
    field = IssueCustomField.create!(:name => 'List', :is_required => true, :field_format => 'list',
179
      :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
180
    @request.session[:user_id] = 2
181
    get(
182
      :issues,
183
      :params => {
184
        :ids => [1, 2]
185
      }
186
    )
187
    assert_select "li.cf_#{field.id}" do
188
      assert_select 'a[href="#"]', :text => 'List'
189
      assert_select 'ul' do
190
        assert_select 'a', 2
191
        assert_select 'a', :text => 'none', :count => 0
192
      end
193
    end
194
  end
195

  
196
  def test_context_menu_on_single_issue_should_select_current_custom_field_value
197
    field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
198
      :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
199
    issue = Issue.find(1)
200
    issue.custom_field_values = {field.id => 'Bar'}
201
    issue.save!
202
    @request.session[:user_id] = 2
203
    get(
204
      :issues,
205
      :params => {
206
        :ids => [1]
207
      }
208
    )
209
    assert_select "li.cf_#{field.id}" do
210
      assert_select 'a[href="#"]', :text => 'List'
211
      assert_select 'ul' do
212
        assert_select 'a', 3
213
        assert_select 'a.icon', :text => 'Bar'
214
      end
215
    end
216
  end
217

  
... This diff was truncated because it exceeds the maximum size that can be displayed.
(3-3/3)