##// END OF EJS Templates
Menu mapper: add support for :before, :after and :last options to #push method and add #delete method....
Jean-Philippe Lang -
r1646:7b8a4fc28bd2
parent child
Show More
@@ -1,134 +1,134
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/mime_type'
3 require 'redmine/mime_type'
4 require 'redmine/core_ext'
4 require 'redmine/core_ext'
5 require 'redmine/themes'
5 require 'redmine/themes'
6 require 'redmine/plugin'
6 require 'redmine/plugin'
7
7
8 begin
8 begin
9 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
9 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
10 rescue LoadError
10 rescue LoadError
11 # RMagick is not available
11 # RMagick is not available
12 end
12 end
13
13
14 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
14 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
15
15
16 # Permissions
16 # Permissions
17 Redmine::AccessControl.map do |map|
17 Redmine::AccessControl.map do |map|
18 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
18 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
19 map.permission :search_project, {:search => :index}, :public => true
19 map.permission :search_project, {:search => :index}, :public => true
20 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
20 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
21 map.permission :select_project_modules, {:projects => :modules}, :require => :member
21 map.permission :select_project_modules, {:projects => :modules}, :require => :member
22 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
22 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
23 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
23 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
24
24
25 map.project_module :issue_tracking do |map|
25 map.project_module :issue_tracking do |map|
26 # Issue categories
26 # Issue categories
27 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
27 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
28 # Issues
28 # Issues
29 map.permission :view_issues, {:projects => [:changelog, :roadmap],
29 map.permission :view_issues, {:projects => [:changelog, :roadmap],
30 :issues => [:index, :changes, :show, :context_menu],
30 :issues => [:index, :changes, :show, :context_menu],
31 :versions => [:show, :status_by],
31 :versions => [:show, :status_by],
32 :queries => :index,
32 :queries => :index,
33 :reports => :issue_report}, :public => true
33 :reports => :issue_report}, :public => true
34 map.permission :add_issues, {:issues => :new}
34 map.permission :add_issues, {:issues => :new}
35 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment]}
35 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment]}
36 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
36 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
37 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
37 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
38 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
38 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
39 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
39 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
40 map.permission :move_issues, {:issues => :move}, :require => :loggedin
40 map.permission :move_issues, {:issues => :move}, :require => :loggedin
41 map.permission :delete_issues, {:issues => :destroy}, :require => :member
41 map.permission :delete_issues, {:issues => :destroy}, :require => :member
42 # Queries
42 # Queries
43 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
43 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
44 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
44 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
45 # Gantt & calendar
45 # Gantt & calendar
46 map.permission :view_gantt, :projects => :gantt
46 map.permission :view_gantt, :projects => :gantt
47 map.permission :view_calendar, :projects => :calendar
47 map.permission :view_calendar, :projects => :calendar
48 end
48 end
49
49
50 map.project_module :time_tracking do |map|
50 map.project_module :time_tracking do |map|
51 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
51 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
52 map.permission :view_time_entries, :timelog => [:details, :report]
52 map.permission :view_time_entries, :timelog => [:details, :report]
53 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
53 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
54 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
54 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
55 end
55 end
56
56
57 map.project_module :news do |map|
57 map.project_module :news do |map|
58 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
58 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
59 map.permission :view_news, {:news => [:index, :show]}, :public => true
59 map.permission :view_news, {:news => [:index, :show]}, :public => true
60 map.permission :comment_news, {:news => :add_comment}
60 map.permission :comment_news, {:news => :add_comment}
61 end
61 end
62
62
63 map.project_module :documents do |map|
63 map.project_module :documents do |map|
64 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
64 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
65 map.permission :view_documents, :documents => [:index, :show, :download]
65 map.permission :view_documents, :documents => [:index, :show, :download]
66 end
66 end
67
67
68 map.project_module :files do |map|
68 map.project_module :files do |map|
69 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
69 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
70 map.permission :view_files, :projects => :list_files, :versions => :download
70 map.permission :view_files, :projects => :list_files, :versions => :download
71 end
71 end
72
72
73 map.project_module :wiki do |map|
73 map.project_module :wiki do |map|
74 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
74 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
75 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
75 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
76 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
76 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
77 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
77 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
78 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
78 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
79 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
79 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
80 end
80 end
81
81
82 map.project_module :repository do |map|
82 map.project_module :repository do |map|
83 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
83 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
84 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
84 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
85 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
85 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
86 end
86 end
87
87
88 map.project_module :boards do |map|
88 map.project_module :boards do |map|
89 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
89 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
90 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
90 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
91 map.permission :add_messages, {:messages => [:new, :reply]}
91 map.permission :add_messages, {:messages => [:new, :reply]}
92 map.permission :edit_messages, {:messages => :edit}, :require => :member
92 map.permission :edit_messages, {:messages => :edit}, :require => :member
93 map.permission :delete_messages, {:messages => :destroy}, :require => :member
93 map.permission :delete_messages, {:messages => :destroy}, :require => :member
94 end
94 end
95 end
95 end
96
96
97 Redmine::MenuManager.map :top_menu do |menu|
97 Redmine::MenuManager.map :top_menu do |menu|
98 menu.push :home, :home_path, :html => { :class => 'home' }
98 menu.push :home, :home_path, :html => { :class => 'home' }
99 menu.push :my_page, { :controller => 'my', :action => 'page' }, :html => { :class => 'mypage' }, :if => Proc.new { User.current.logged? }
99 menu.push :my_page, { :controller => 'my', :action => 'page' }, :html => { :class => 'mypage' }, :if => Proc.new { User.current.logged? }
100 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural, :html => { :class => 'projects' }
100 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural, :html => { :class => 'projects' }
101 menu.push :administration, { :controller => 'admin', :action => 'index' }, :html => { :class => 'admin' }, :if => Proc.new { User.current.admin? }
101 menu.push :administration, { :controller => 'admin', :action => 'index' }, :html => { :class => 'admin' }, :if => Proc.new { User.current.admin? }
102 menu.push :help, Redmine::Info.help_url, :html => { :class => 'help' }
102 menu.push :help, Redmine::Info.help_url, :html => { :class => 'help' }
103 end
103 end
104
104
105 Redmine::MenuManager.map :account_menu do |menu|
105 Redmine::MenuManager.map :account_menu do |menu|
106 menu.push :login, :signin_path, :html => { :class => 'login' }, :if => Proc.new { !User.current.logged? }
106 menu.push :login, :signin_path, :html => { :class => 'login' }, :if => Proc.new { !User.current.logged? }
107 menu.push :register, { :controller => 'account', :action => 'register' }, :html => { :class => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
107 menu.push :register, { :controller => 'account', :action => 'register' }, :html => { :class => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
108 menu.push :my_account, { :controller => 'my', :action => 'account' }, :html => { :class => 'myaccount' }, :if => Proc.new { User.current.logged? }
108 menu.push :my_account, { :controller => 'my', :action => 'account' }, :html => { :class => 'myaccount' }, :if => Proc.new { User.current.logged? }
109 menu.push :logout, :signout_path, :html => { :class => 'logout' }, :if => Proc.new { User.current.logged? }
109 menu.push :logout, :signout_path, :html => { :class => 'logout' }, :if => Proc.new { User.current.logged? }
110 end
110 end
111
111
112 Redmine::MenuManager.map :application_menu do |menu|
112 Redmine::MenuManager.map :application_menu do |menu|
113 # Empty
113 # Empty
114 end
114 end
115
115
116 Redmine::MenuManager.map :project_menu do |menu|
116 Redmine::MenuManager.map :project_menu do |menu|
117 menu.push :overview, { :controller => 'projects', :action => 'show' }
117 menu.push :overview, { :controller => 'projects', :action => 'show' }
118 menu.push :activity, { :controller => 'projects', :action => 'activity' }
118 menu.push :activity, { :controller => 'projects', :action => 'activity' }
119 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
119 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
120 :if => Proc.new { |p| p.versions.any? }
120 :if => Proc.new { |p| p.versions.any? }
121 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
121 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
122 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
122 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
123 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
123 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
124 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
124 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
125 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
125 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
126 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
126 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
127 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
127 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
128 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
128 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
129 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
129 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
130 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
130 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
131 menu.push :repository, { :controller => 'repositories', :action => 'show' },
131 menu.push :repository, { :controller => 'repositories', :action => 'show' },
132 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
132 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
133 menu.push :settings, { :controller => 'projects', :action => 'settings' }
133 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
134 end
134 end
@@ -1,154 +1,178
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'gloc'
18 require 'gloc'
19
19
20 module Redmine
20 module Redmine
21 module MenuManager
21 module MenuManager
22 module MenuController
22 module MenuController
23 def self.included(base)
23 def self.included(base)
24 base.extend(ClassMethods)
24 base.extend(ClassMethods)
25 end
25 end
26
26
27 module ClassMethods
27 module ClassMethods
28 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
28 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
29 mattr_accessor :menu_items
29 mattr_accessor :menu_items
30
30
31 # Set the menu item name for a controller or specific actions
31 # Set the menu item name for a controller or specific actions
32 # Examples:
32 # Examples:
33 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
33 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
34 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
34 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
35 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
35 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
36 #
36 #
37 # The default menu item name for a controller is controller_name by default
37 # The default menu item name for a controller is controller_name by default
38 # Eg. the default menu item name for ProjectsController is :projects
38 # Eg. the default menu item name for ProjectsController is :projects
39 def menu_item(id, options = {})
39 def menu_item(id, options = {})
40 if actions = options[:only]
40 if actions = options[:only]
41 actions = [] << actions unless actions.is_a?(Array)
41 actions = [] << actions unless actions.is_a?(Array)
42 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
42 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
43 else
43 else
44 menu_items[controller_name.to_sym][:default] = id
44 menu_items[controller_name.to_sym][:default] = id
45 end
45 end
46 end
46 end
47 end
47 end
48
48
49 def menu_items
49 def menu_items
50 self.class.menu_items
50 self.class.menu_items
51 end
51 end
52
52
53 # Returns the menu item name according to the current action
53 # Returns the menu item name according to the current action
54 def current_menu_item
54 def current_menu_item
55 menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
55 menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
56 menu_items[controller_name.to_sym][:default]
56 menu_items[controller_name.to_sym][:default]
57 end
57 end
58 end
58 end
59
59
60 module MenuHelper
60 module MenuHelper
61 # Returns the current menu item name
61 # Returns the current menu item name
62 def current_menu_item
62 def current_menu_item
63 @controller.current_menu_item
63 @controller.current_menu_item
64 end
64 end
65
65
66 # Renders the application main menu
66 # Renders the application main menu
67 def render_main_menu(project)
67 def render_main_menu(project)
68 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
68 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
69 end
69 end
70
70
71 def render_menu(menu, project=nil)
71 def render_menu(menu, project=nil)
72 links = []
72 links = []
73 Redmine::MenuManager.allowed_items(menu, User.current, project).each do |item|
73 Redmine::MenuManager.allowed_items(menu, User.current, project).each do |item|
74 unless item.condition && !item.condition.call(project)
74 unless item.condition && !item.condition.call(project)
75 url = case item.url
75 url = case item.url
76 when Hash
76 when Hash
77 project.nil? ? item.url : {item.param => project}.merge(item.url)
77 project.nil? ? item.url : {item.param => project}.merge(item.url)
78 when Symbol
78 when Symbol
79 send(item.url)
79 send(item.url)
80 else
80 else
81 item.url
81 item.url
82 end
82 end
83 caption = item.caption(project)
83 caption = item.caption(project)
84 caption = l(caption) if caption.is_a?(Symbol)
84 caption = l(caption) if caption.is_a?(Symbol)
85 links << content_tag('li',
85 links << content_tag('li',
86 link_to(h(caption), url, (current_menu_item == item.name ? item.html_options.merge(:class => 'selected') : item.html_options)))
86 link_to(h(caption), url, (current_menu_item == item.name ? item.html_options.merge(:class => 'selected') : item.html_options)))
87 end
87 end
88 end
88 end
89 links.empty? ? nil : content_tag('ul', links.join("\n"))
89 links.empty? ? nil : content_tag('ul', links.join("\n"))
90 end
90 end
91 end
91 end
92
92
93 class << self
93 class << self
94 def map(menu_name)
94 def map(menu_name)
95 mapper = Mapper.new
96 yield mapper
97 @items ||= {}
95 @items ||= {}
98 @items[menu_name.to_sym] ||= []
96 mapper = Mapper.new(menu_name.to_sym, @items)
99 @items[menu_name.to_sym] += mapper.items
97 yield mapper
100 end
98 end
101
99
102 def items(menu_name)
100 def items(menu_name)
103 @items[menu_name.to_sym] || []
101 @items[menu_name.to_sym] || []
104 end
102 end
105
103
106 def allowed_items(menu_name, user, project)
104 def allowed_items(menu_name, user, project)
107 project ? items(menu_name).select {|item| user && user.allowed_to?(item.url, project)} : items(menu_name)
105 project ? items(menu_name).select {|item| user && user.allowed_to?(item.url, project)} : items(menu_name)
108 end
106 end
109 end
107 end
110
108
111 class Mapper
109 class Mapper
110 def initialize(menu, items)
111 items[menu] ||= []
112 @menu = menu
113 @menu_items = items[menu]
114 end
115
116 @@last_items_count = Hash.new {|h,k| h[k] = 0}
117
112 # Adds an item at the end of the menu. Available options:
118 # Adds an item at the end of the menu. Available options:
113 # * param: the parameter name that is used for the project id (default is :id)
119 # * param: the parameter name that is used for the project id (default is :id)
114 # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true
120 # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true
115 # * caption that can be:
121 # * caption that can be:
116 # * a localized string Symbol
122 # * a localized string Symbol
117 # * a String
123 # * a String
118 # * a Proc that can take the project as argument
124 # * a Proc that can take the project as argument
125 # * before, after: specify where the menu item should be inserted (eg. :after => :activity)
126 # * last: menu item will stay at the end (eg. :last => true)
119 # * html_options: a hash of html options that are passed to link_to
127 # * html_options: a hash of html options that are passed to link_to
120 def push(name, url, options={})
128 def push(name, url, options={})
121 items << MenuItem.new(name, url, options)
129 options = options.dup
130
131 # menu item position
132 if before = options.delete(:before)
133 position = @menu_items.index {|i| i.name == before}
134 elsif after = options.delete(:after)
135 position = @menu_items.index {|i| i.name == after}
136 position += 1 unless position.nil?
137 elsif options.delete(:last)
138 position = @menu_items.size
139 @@last_items_count[@menu] += 1
140 end
141 # default position
142 position ||= @menu_items.size - @@last_items_count[@menu]
143
144 @menu_items.insert(position, MenuItem.new(name, url, options))
122 end
145 end
123
146
124 def items
147 # Removes a menu item
125 @items ||= []
148 def delete(name)
149 @menu_items.delete_if {|i| i.name == name}
126 end
150 end
127 end
151 end
128
152
129 class MenuItem
153 class MenuItem
130 include GLoc
154 include GLoc
131 attr_reader :name, :url, :param, :condition, :html_options
155 attr_reader :name, :url, :param, :condition, :html_options
132
156
133 def initialize(name, url, options)
157 def initialize(name, url, options)
134 raise "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
158 raise "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
135 raise "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
159 raise "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
136 @name = name
160 @name = name
137 @url = url
161 @url = url
138 @condition = options[:if]
162 @condition = options[:if]
139 @param = options[:param] || :id
163 @param = options[:param] || :id
140 @caption = options[:caption]
164 @caption = options[:caption]
141 @html_options = options[:html] || {}
165 @html_options = options[:html] || {}
142 end
166 end
143
167
144 def caption(project=nil)
168 def caption(project=nil)
145 if @caption.is_a?(Proc)
169 if @caption.is_a?(Proc)
146 @caption.call(project)
170 @caption.call(project)
147 else
171 else
148 # check if localized string exists on first render (after GLoc strings are loaded)
172 # check if localized string exists on first render (after GLoc strings are loaded)
149 @caption_key ||= (@caption || (l_has_string?("label_#{@name}".to_sym) ? "label_#{@name}".to_sym : @name.to_s.humanize))
173 @caption_key ||= (@caption || (l_has_string?("label_#{@name}".to_sym) ? "label_#{@name}".to_sym : @name.to_s.humanize))
150 end
174 end
151 end
175 end
152 end
176 end
153 end
177 end
154 end
178 end
@@ -1,315 +1,344
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'projects_controller'
19 require 'projects_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class ProjectsController; def rescue_action(e) raise e end; end
22 class ProjectsController; def rescue_action(e) raise e end; end
23
23
24 class ProjectsControllerTest < Test::Unit::TestCase
24 class ProjectsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
27
27
28 def setup
28 def setup
29 @controller = ProjectsController.new
29 @controller = ProjectsController.new
30 @request = ActionController::TestRequest.new
30 @request = ActionController::TestRequest.new
31 @response = ActionController::TestResponse.new
31 @response = ActionController::TestResponse.new
32 @request.session[:user_id] = nil
32 @request.session[:user_id] = nil
33 end
33 end
34
34
35 def test_index
35 def test_index
36 get :index
36 get :index
37 assert_response :success
37 assert_response :success
38 assert_template 'index'
38 assert_template 'index'
39 assert_not_nil assigns(:project_tree)
39 assert_not_nil assigns(:project_tree)
40 # Root project as hash key
40 # Root project as hash key
41 assert assigns(:project_tree).keys.include?(Project.find(1))
41 assert assigns(:project_tree).keys.include?(Project.find(1))
42 # Subproject in corresponding value
42 # Subproject in corresponding value
43 assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
43 assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
44 end
44 end
45
45
46 def test_index_atom
46 def test_index_atom
47 get :index, :format => 'atom'
47 get :index, :format => 'atom'
48 assert_response :success
48 assert_response :success
49 assert_template 'common/feed.atom.rxml'
49 assert_template 'common/feed.atom.rxml'
50 assert_select 'feed>title', :text => 'Redmine: Latest projects'
50 assert_select 'feed>title', :text => 'Redmine: Latest projects'
51 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
51 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
52 end
52 end
53
53
54 def test_show_by_id
54 def test_show_by_id
55 get :show, :id => 1
55 get :show, :id => 1
56 assert_response :success
56 assert_response :success
57 assert_template 'show'
57 assert_template 'show'
58 assert_not_nil assigns(:project)
58 assert_not_nil assigns(:project)
59 end
59 end
60
60
61 def test_show_by_identifier
61 def test_show_by_identifier
62 get :show, :id => 'ecookbook'
62 get :show, :id => 'ecookbook'
63 assert_response :success
63 assert_response :success
64 assert_template 'show'
64 assert_template 'show'
65 assert_not_nil assigns(:project)
65 assert_not_nil assigns(:project)
66 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
66 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
67 end
67 end
68
68
69 def test_private_subprojects_hidden
69 def test_private_subprojects_hidden
70 get :show, :id => 'ecookbook'
70 get :show, :id => 'ecookbook'
71 assert_response :success
71 assert_response :success
72 assert_template 'show'
72 assert_template 'show'
73 assert_no_tag :tag => 'a', :content => /Private child/
73 assert_no_tag :tag => 'a', :content => /Private child/
74 end
74 end
75
75
76 def test_private_subprojects_visible
76 def test_private_subprojects_visible
77 @request.session[:user_id] = 2 # manager who is a member of the private subproject
77 @request.session[:user_id] = 2 # manager who is a member of the private subproject
78 get :show, :id => 'ecookbook'
78 get :show, :id => 'ecookbook'
79 assert_response :success
79 assert_response :success
80 assert_template 'show'
80 assert_template 'show'
81 assert_tag :tag => 'a', :content => /Private child/
81 assert_tag :tag => 'a', :content => /Private child/
82 end
82 end
83
83
84 def test_settings
84 def test_settings
85 @request.session[:user_id] = 2 # manager
85 @request.session[:user_id] = 2 # manager
86 get :settings, :id => 1
86 get :settings, :id => 1
87 assert_response :success
87 assert_response :success
88 assert_template 'settings'
88 assert_template 'settings'
89 end
89 end
90
90
91 def test_edit
91 def test_edit
92 @request.session[:user_id] = 2 # manager
92 @request.session[:user_id] = 2 # manager
93 post :edit, :id => 1, :project => {:name => 'Test changed name',
93 post :edit, :id => 1, :project => {:name => 'Test changed name',
94 :issue_custom_field_ids => ['']}
94 :issue_custom_field_ids => ['']}
95 assert_redirected_to 'projects/settings/ecookbook'
95 assert_redirected_to 'projects/settings/ecookbook'
96 project = Project.find(1)
96 project = Project.find(1)
97 assert_equal 'Test changed name', project.name
97 assert_equal 'Test changed name', project.name
98 end
98 end
99
99
100 def test_get_destroy
100 def test_get_destroy
101 @request.session[:user_id] = 1 # admin
101 @request.session[:user_id] = 1 # admin
102 get :destroy, :id => 1
102 get :destroy, :id => 1
103 assert_response :success
103 assert_response :success
104 assert_template 'destroy'
104 assert_template 'destroy'
105 assert_not_nil Project.find_by_id(1)
105 assert_not_nil Project.find_by_id(1)
106 end
106 end
107
107
108 def test_post_destroy
108 def test_post_destroy
109 @request.session[:user_id] = 1 # admin
109 @request.session[:user_id] = 1 # admin
110 post :destroy, :id => 1, :confirm => 1
110 post :destroy, :id => 1, :confirm => 1
111 assert_redirected_to 'admin/projects'
111 assert_redirected_to 'admin/projects'
112 assert_nil Project.find_by_id(1)
112 assert_nil Project.find_by_id(1)
113 end
113 end
114
114
115 def test_list_files
115 def test_list_files
116 get :list_files, :id => 1
116 get :list_files, :id => 1
117 assert_response :success
117 assert_response :success
118 assert_template 'list_files'
118 assert_template 'list_files'
119 assert_not_nil assigns(:versions)
119 assert_not_nil assigns(:versions)
120 end
120 end
121
121
122 def test_changelog
122 def test_changelog
123 get :changelog, :id => 1
123 get :changelog, :id => 1
124 assert_response :success
124 assert_response :success
125 assert_template 'changelog'
125 assert_template 'changelog'
126 assert_not_nil assigns(:versions)
126 assert_not_nil assigns(:versions)
127 end
127 end
128
128
129 def test_roadmap
129 def test_roadmap
130 get :roadmap, :id => 1
130 get :roadmap, :id => 1
131 assert_response :success
131 assert_response :success
132 assert_template 'roadmap'
132 assert_template 'roadmap'
133 assert_not_nil assigns(:versions)
133 assert_not_nil assigns(:versions)
134 # Version with no date set appears
134 # Version with no date set appears
135 assert assigns(:versions).include?(Version.find(3))
135 assert assigns(:versions).include?(Version.find(3))
136 # Completed version doesn't appear
136 # Completed version doesn't appear
137 assert !assigns(:versions).include?(Version.find(1))
137 assert !assigns(:versions).include?(Version.find(1))
138 end
138 end
139
139
140 def test_roadmap_with_completed_versions
140 def test_roadmap_with_completed_versions
141 get :roadmap, :id => 1, :completed => 1
141 get :roadmap, :id => 1, :completed => 1
142 assert_response :success
142 assert_response :success
143 assert_template 'roadmap'
143 assert_template 'roadmap'
144 assert_not_nil assigns(:versions)
144 assert_not_nil assigns(:versions)
145 # Version with no date set appears
145 # Version with no date set appears
146 assert assigns(:versions).include?(Version.find(3))
146 assert assigns(:versions).include?(Version.find(3))
147 # Completed version appears
147 # Completed version appears
148 assert assigns(:versions).include?(Version.find(1))
148 assert assigns(:versions).include?(Version.find(1))
149 end
149 end
150
150
151 def test_project_activity
151 def test_project_activity
152 get :activity, :id => 1, :with_subprojects => 0
152 get :activity, :id => 1, :with_subprojects => 0
153 assert_response :success
153 assert_response :success
154 assert_template 'activity'
154 assert_template 'activity'
155 assert_not_nil assigns(:events_by_day)
155 assert_not_nil assigns(:events_by_day)
156 assert_not_nil assigns(:events)
156 assert_not_nil assigns(:events)
157
157
158 # subproject issue not included by default
158 # subproject issue not included by default
159 assert !assigns(:events).include?(Issue.find(5))
159 assert !assigns(:events).include?(Issue.find(5))
160
160
161 assert_tag :tag => "h3",
161 assert_tag :tag => "h3",
162 :content => /#{2.days.ago.to_date.day}/,
162 :content => /#{2.days.ago.to_date.day}/,
163 :sibling => { :tag => "dl",
163 :sibling => { :tag => "dl",
164 :child => { :tag => "dt",
164 :child => { :tag => "dt",
165 :attributes => { :class => /issue-edit/ },
165 :attributes => { :class => /issue-edit/ },
166 :child => { :tag => "a",
166 :child => { :tag => "a",
167 :content => /(#{IssueStatus.find(2).name})/,
167 :content => /(#{IssueStatus.find(2).name})/,
168 }
168 }
169 }
169 }
170 }
170 }
171
171
172 get :activity, :id => 1, :from => 3.days.ago.to_date
172 get :activity, :id => 1, :from => 3.days.ago.to_date
173 assert_response :success
173 assert_response :success
174 assert_template 'activity'
174 assert_template 'activity'
175 assert_not_nil assigns(:events_by_day)
175 assert_not_nil assigns(:events_by_day)
176
176
177 assert_tag :tag => "h3",
177 assert_tag :tag => "h3",
178 :content => /#{3.day.ago.to_date.day}/,
178 :content => /#{3.day.ago.to_date.day}/,
179 :sibling => { :tag => "dl",
179 :sibling => { :tag => "dl",
180 :child => { :tag => "dt",
180 :child => { :tag => "dt",
181 :attributes => { :class => /issue/ },
181 :attributes => { :class => /issue/ },
182 :child => { :tag => "a",
182 :child => { :tag => "a",
183 :content => /#{Issue.find(1).subject}/,
183 :content => /#{Issue.find(1).subject}/,
184 }
184 }
185 }
185 }
186 }
186 }
187 end
187 end
188
188
189 def test_activity_with_subprojects
189 def test_activity_with_subprojects
190 get :activity, :id => 1, :with_subprojects => 1
190 get :activity, :id => 1, :with_subprojects => 1
191 assert_response :success
191 assert_response :success
192 assert_template 'activity'
192 assert_template 'activity'
193 assert_not_nil assigns(:events)
193 assert_not_nil assigns(:events)
194
194
195 assert assigns(:events).include?(Issue.find(1))
195 assert assigns(:events).include?(Issue.find(1))
196 assert !assigns(:events).include?(Issue.find(4))
196 assert !assigns(:events).include?(Issue.find(4))
197 # subproject issue
197 # subproject issue
198 assert assigns(:events).include?(Issue.find(5))
198 assert assigns(:events).include?(Issue.find(5))
199 end
199 end
200
200
201 def test_global_activity_anonymous
201 def test_global_activity_anonymous
202 get :activity
202 get :activity
203 assert_response :success
203 assert_response :success
204 assert_template 'activity'
204 assert_template 'activity'
205 assert_not_nil assigns(:events)
205 assert_not_nil assigns(:events)
206
206
207 assert assigns(:events).include?(Issue.find(1))
207 assert assigns(:events).include?(Issue.find(1))
208 # Issue of a private project
208 # Issue of a private project
209 assert !assigns(:events).include?(Issue.find(4))
209 assert !assigns(:events).include?(Issue.find(4))
210 end
210 end
211
211
212 def test_global_activity_logged_user
212 def test_global_activity_logged_user
213 @request.session[:user_id] = 2 # manager
213 @request.session[:user_id] = 2 # manager
214 get :activity
214 get :activity
215 assert_response :success
215 assert_response :success
216 assert_template 'activity'
216 assert_template 'activity'
217 assert_not_nil assigns(:events)
217 assert_not_nil assigns(:events)
218
218
219 assert assigns(:events).include?(Issue.find(1))
219 assert assigns(:events).include?(Issue.find(1))
220 # Issue of a private project the user belongs to
220 # Issue of a private project the user belongs to
221 assert assigns(:events).include?(Issue.find(4))
221 assert assigns(:events).include?(Issue.find(4))
222 end
222 end
223
223
224
224
225 def test_global_activity_with_all_types
225 def test_global_activity_with_all_types
226 get :activity, :show_issues => 1, :show_news => 1, :show_files => 1, :show_documents => 1, :show_changesets => 1, :show_wiki_pages => 1, :show_messages => 1
226 get :activity, :show_issues => 1, :show_news => 1, :show_files => 1, :show_documents => 1, :show_changesets => 1, :show_wiki_pages => 1, :show_messages => 1
227 assert_response :success
227 assert_response :success
228 assert_template 'activity'
228 assert_template 'activity'
229 assert_not_nil assigns(:events)
229 assert_not_nil assigns(:events)
230
230
231 assert assigns(:events).include?(Issue.find(1))
231 assert assigns(:events).include?(Issue.find(1))
232 assert !assigns(:events).include?(Issue.find(4))
232 assert !assigns(:events).include?(Issue.find(4))
233 assert assigns(:events).include?(Message.find(5))
233 assert assigns(:events).include?(Message.find(5))
234 end
234 end
235
235
236 def test_calendar
236 def test_calendar
237 get :calendar, :id => 1
237 get :calendar, :id => 1
238 assert_response :success
238 assert_response :success
239 assert_template 'calendar'
239 assert_template 'calendar'
240 assert_not_nil assigns(:calendar)
240 assert_not_nil assigns(:calendar)
241 end
241 end
242
242
243 def test_calendar_with_subprojects_should_not_show_private_subprojects
243 def test_calendar_with_subprojects_should_not_show_private_subprojects
244 get :calendar, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
244 get :calendar, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
245 assert_response :success
245 assert_response :success
246 assert_template 'calendar'
246 assert_template 'calendar'
247 assert_not_nil assigns(:calendar)
247 assert_not_nil assigns(:calendar)
248 assert_no_tag :tag => 'a', :content => /#6/
248 assert_no_tag :tag => 'a', :content => /#6/
249 end
249 end
250
250
251 def test_calendar_with_subprojects_should_show_private_subprojects
251 def test_calendar_with_subprojects_should_show_private_subprojects
252 @request.session[:user_id] = 2
252 @request.session[:user_id] = 2
253 get :calendar, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
253 get :calendar, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
254 assert_response :success
254 assert_response :success
255 assert_template 'calendar'
255 assert_template 'calendar'
256 assert_not_nil assigns(:calendar)
256 assert_not_nil assigns(:calendar)
257 assert_tag :tag => 'a', :content => /#6/
257 assert_tag :tag => 'a', :content => /#6/
258 end
258 end
259
259
260 def test_gantt
260 def test_gantt
261 get :gantt, :id => 1
261 get :gantt, :id => 1
262 assert_response :success
262 assert_response :success
263 assert_template 'gantt.rhtml'
263 assert_template 'gantt.rhtml'
264 events = assigns(:events)
264 events = assigns(:events)
265 assert_not_nil events
265 assert_not_nil events
266 # Issue with start and due dates
266 # Issue with start and due dates
267 i = Issue.find(1)
267 i = Issue.find(1)
268 assert_not_nil i.due_date
268 assert_not_nil i.due_date
269 assert events.include?(Issue.find(1))
269 assert events.include?(Issue.find(1))
270 # Issue with without due date but targeted to a version with date
270 # Issue with without due date but targeted to a version with date
271 i = Issue.find(2)
271 i = Issue.find(2)
272 assert_nil i.due_date
272 assert_nil i.due_date
273 assert events.include?(i)
273 assert events.include?(i)
274 end
274 end
275
275
276 def test_gantt_with_subprojects_should_not_show_private_subprojects
276 def test_gantt_with_subprojects_should_not_show_private_subprojects
277 get :gantt, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
277 get :gantt, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
278 assert_response :success
278 assert_response :success
279 assert_template 'gantt.rhtml'
279 assert_template 'gantt.rhtml'
280 assert_not_nil assigns(:events)
280 assert_not_nil assigns(:events)
281 assert_no_tag :tag => 'a', :content => /#6/
281 assert_no_tag :tag => 'a', :content => /#6/
282 end
282 end
283
283
284 def test_gantt_with_subprojects_should_show_private_subprojects
284 def test_gantt_with_subprojects_should_show_private_subprojects
285 @request.session[:user_id] = 2
285 @request.session[:user_id] = 2
286 get :gantt, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
286 get :gantt, :id => 1, :with_subprojects => 1, :tracker_ids => [1, 2]
287 assert_response :success
287 assert_response :success
288 assert_template 'gantt.rhtml'
288 assert_template 'gantt.rhtml'
289 assert_not_nil assigns(:events)
289 assert_not_nil assigns(:events)
290 assert_tag :tag => 'a', :content => /#6/
290 assert_tag :tag => 'a', :content => /#6/
291 end
291 end
292
292
293 def test_gantt_export_to_pdf
293 def test_gantt_export_to_pdf
294 get :gantt, :id => 1, :format => 'pdf'
294 get :gantt, :id => 1, :format => 'pdf'
295 assert_response :success
295 assert_response :success
296 assert_template 'gantt.rfpdf'
296 assert_template 'gantt.rfpdf'
297 assert_equal 'application/pdf', @response.content_type
297 assert_equal 'application/pdf', @response.content_type
298 assert_not_nil assigns(:events)
298 assert_not_nil assigns(:events)
299 end
299 end
300
300
301 def test_archive
301 def test_archive
302 @request.session[:user_id] = 1 # admin
302 @request.session[:user_id] = 1 # admin
303 post :archive, :id => 1
303 post :archive, :id => 1
304 assert_redirected_to 'admin/projects'
304 assert_redirected_to 'admin/projects'
305 assert !Project.find(1).active?
305 assert !Project.find(1).active?
306 end
306 end
307
307
308 def test_unarchive
308 def test_unarchive
309 @request.session[:user_id] = 1 # admin
309 @request.session[:user_id] = 1 # admin
310 Project.find(1).archive
310 Project.find(1).archive
311 post :unarchive, :id => 1
311 post :unarchive, :id => 1
312 assert_redirected_to 'admin/projects'
312 assert_redirected_to 'admin/projects'
313 assert Project.find(1).active?
313 assert Project.find(1).active?
314 end
314 end
315
316 def test_project_menu
317 assert_no_difference 'Redmine::MenuManager.items(:project_menu).size' do
318 Redmine::MenuManager.map :project_menu do |menu|
319 menu.push :foo, { :controller => 'projects', :action => 'show' }, :cation => 'Foo'
320 menu.push :bar, { :controller => 'projects', :action => 'show' }, :before => :activity
321 menu.push :hello, { :controller => 'projects', :action => 'show' }, :caption => Proc.new {|p| p.name.upcase }, :after => :bar
322 end
323
324 get :show, :id => 1
325 assert_tag :div, :attributes => { :id => 'main-menu' },
326 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo' } }
327
328 assert_tag :div, :attributes => { :id => 'main-menu' },
329 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar' },
330 :before => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' } } }
331
332 assert_tag :div, :attributes => { :id => 'main-menu' },
333 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' },
334 :before => { :tag => 'li', :child => { :tag => 'a', :content => 'Activity' } } }
335
336 # Remove the menu items
337 Redmine::MenuManager.map :project_menu do |menu|
338 menu.delete :foo
339 menu.delete :bar
340 menu.delete :hello
341 end
342 end
343 end
315 end
344 end
General Comments 0
You need to be logged in to leave comments. Login now