##// END OF EJS Templates
Fix an IndexError if all the :last menu items are deleted from a menu. #4718...
Eric Davis -
r3333:28e9bc5d823e
parent child
Show More
@@ -1,434 +1,443
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'tree' # gem install rubytree
19 19
20 20 # Monkey patch the TreeNode to add on a few more methods :nodoc:
21 21 module TreeNodePatch
22 22 def self.included(base)
23 23 base.class_eval do
24 24 attr_reader :last_items_count
25 25
26 26 alias :old_initilize :initialize
27 27 def initialize(name, content = nil)
28 28 old_initilize(name, content)
29 29 @last_items_count = 0
30 30 extend(InstanceMethods)
31 31 end
32 32 end
33 33 end
34 34
35 35 module InstanceMethods
36 36 # Adds the specified child node to the receiver node. The child node's
37 37 # parent is set to be the receiver. The child is added as the first child in
38 38 # the current list of children for the receiver node.
39 39 def prepend(child)
40 40 raise "Child already added" if @childrenHash.has_key?(child.name)
41 41
42 42 @childrenHash[child.name] = child
43 43 @children = [child] + @children
44 44 child.parent = self
45 45 return child
46 46
47 47 end
48 48
49 49 # Adds the specified child node to the receiver node. The child node's
50 50 # parent is set to be the receiver. The child is added at the position
51 51 # into the current list of children for the receiver node.
52 52 def add_at(child, position)
53 53 raise "Child already added" if @childrenHash.has_key?(child.name)
54 54
55 55 @childrenHash[child.name] = child
56 56 @children = @children.insert(position, child)
57 57 child.parent = self
58 58 return child
59 59
60 60 end
61 61
62 62 def add_last(child)
63 63 raise "Child already added" if @childrenHash.has_key?(child.name)
64 64
65 65 @childrenHash[child.name] = child
66 66 @children << child
67 67 @last_items_count += 1
68 68 child.parent = self
69 69 return child
70 70
71 71 end
72 72
73 73 # Adds the specified child node to the receiver node. The child node's
74 74 # parent is set to be the receiver. The child is added as the last child in
75 75 # the current list of children for the receiver node.
76 76 def add(child)
77 77 raise "Child already added" if @childrenHash.has_key?(child.name)
78 78
79 79 @childrenHash[child.name] = child
80 80 position = @children.size - @last_items_count
81 81 @children.insert(position, child)
82 82 child.parent = self
83 83 return child
84 84
85 85 end
86 86
87 # Wrapp remove! making sure to decrement the last_items counter if
88 # the removed child was a last item
89 def remove!(child)
90 @last_items_count -= +1 if child && child.last
91 super
92 end
93
94
87 95 # Will return the position (zero-based) of the current child in
88 96 # it's parent
89 97 def position
90 98 self.parent.children.index(self)
91 99 end
92 100 end
93 101 end
94 102 Tree::TreeNode.send(:include, TreeNodePatch)
95 103
96 104 module Redmine
97 105 module MenuManager
98 106 class MenuError < StandardError #:nodoc:
99 107 end
100 108
101 109 module MenuController
102 110 def self.included(base)
103 111 base.extend(ClassMethods)
104 112 end
105 113
106 114 module ClassMethods
107 115 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
108 116 mattr_accessor :menu_items
109 117
110 118 # Set the menu item name for a controller or specific actions
111 119 # Examples:
112 120 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
113 121 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
114 122 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
115 123 #
116 124 # The default menu item name for a controller is controller_name by default
117 125 # Eg. the default menu item name for ProjectsController is :projects
118 126 def menu_item(id, options = {})
119 127 if actions = options[:only]
120 128 actions = [] << actions unless actions.is_a?(Array)
121 129 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
122 130 else
123 131 menu_items[controller_name.to_sym][:default] = id
124 132 end
125 133 end
126 134 end
127 135
128 136 def menu_items
129 137 self.class.menu_items
130 138 end
131 139
132 140 # Returns the menu item name according to the current action
133 141 def current_menu_item
134 142 @current_menu_item ||= menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
135 143 menu_items[controller_name.to_sym][:default]
136 144 end
137 145
138 146 # Redirects user to the menu item of the given project
139 147 # Returns false if user is not authorized
140 148 def redirect_to_project_menu_item(project, name)
141 149 item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name.to_s == name.to_s}
142 150 if item && User.current.allowed_to?(item.url, project) && (item.condition.nil? || item.condition.call(project))
143 151 redirect_to({item.param => project}.merge(item.url))
144 152 return true
145 153 end
146 154 false
147 155 end
148 156 end
149 157
150 158 module MenuHelper
151 159 # Returns the current menu item name
152 160 def current_menu_item
153 161 @controller.current_menu_item
154 162 end
155 163
156 164 # Renders the application main menu
157 165 def render_main_menu(project)
158 166 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
159 167 end
160 168
161 169 def render_menu(menu, project=nil)
162 170 links = []
163 171 menu_items_for(menu, project) do |node|
164 172 links << render_menu_node(node, project)
165 173 end
166 174 links.empty? ? nil : content_tag('ul', links.join("\n"))
167 175 end
168 176
169 177 def render_menu_node(node, project=nil)
170 178 if node.hasChildren? || !node.child_menus.nil?
171 179 return render_menu_node_with_children(node, project)
172 180 else
173 181 caption, url, selected = extract_node_details(node, project)
174 182 return content_tag('li',
175 183 render_single_menu_node(node, caption, url, selected))
176 184 end
177 185 end
178 186
179 187 def render_menu_node_with_children(node, project=nil)
180 188 caption, url, selected = extract_node_details(node, project)
181 189
182 190 html = returning [] do |html|
183 191 html << '<li>'
184 192 # Parent
185 193 html << render_single_menu_node(node, caption, url, selected)
186 194
187 195 # Standard children
188 196 standard_children_list = returning "" do |child_html|
189 197 node.children.each do |child|
190 198 child_html << render_menu_node(child, project)
191 199 end
192 200 end
193 201
194 202 html << content_tag(:ul, standard_children_list, :class => 'menu-children') unless standard_children_list.empty?
195 203
196 204 # Unattached children
197 205 unattached_children_list = render_unattached_children_menu(node, project)
198 206 html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank?
199 207
200 208 html << '</li>'
201 209 end
202 210 return html.join("\n")
203 211 end
204 212
205 213 # Returns a list of unattached children menu items
206 214 def render_unattached_children_menu(node, project)
207 215 return nil unless node.child_menus
208 216
209 217 returning "" do |child_html|
210 218 unattached_children = node.child_menus.call(project)
211 219 # Tree nodes support #each so we need to do object detection
212 220 if unattached_children.is_a? Array
213 221 unattached_children.each do |child|
214 222 child_html << content_tag(:li, render_unattached_menu_item(child, project))
215 223 end
216 224 else
217 225 raise MenuError, ":child_menus must be an array of MenuItems"
218 226 end
219 227 end
220 228 end
221 229
222 230 def render_single_menu_node(item, caption, url, selected)
223 231 link_to(h(caption), url, item.html_options(:selected => selected))
224 232 end
225 233
226 234 def render_unattached_menu_item(menu_item, project)
227 235 raise MenuError, ":child_menus must be an array of MenuItems" unless menu_item.is_a? MenuItem
228 236
229 237 if User.current.allowed_to?(menu_item.url, project)
230 238 link_to(h(menu_item.caption),
231 239 menu_item.url,
232 240 menu_item.html_options)
233 241 end
234 242 end
235 243
236 244 def menu_items_for(menu, project=nil)
237 245 items = []
238 246 Redmine::MenuManager.items(menu).root.children.each do |node|
239 247 if allowed_node?(node, User.current, project)
240 248 if block_given?
241 249 yield node
242 250 else
243 251 items << node # TODO: not used?
244 252 end
245 253 end
246 254 end
247 255 return block_given? ? nil : items
248 256 end
249 257
250 258 def extract_node_details(node, project=nil)
251 259 item = node
252 260 url = case item.url
253 261 when Hash
254 262 project.nil? ? item.url : {item.param => project}.merge(item.url)
255 263 when Symbol
256 264 send(item.url)
257 265 else
258 266 item.url
259 267 end
260 268 caption = item.caption(project)
261 269 return [caption, url, (current_menu_item == item.name)]
262 270 end
263 271
264 272 # Checks if a user is allowed to access the menu item by:
265 273 #
266 274 # * Checking the conditions of the item
267 275 # * Checking the url target (project only)
268 276 def allowed_node?(node, user, project)
269 277 if node.condition && !node.condition.call(project)
270 278 # Condition that doesn't pass
271 279 return false
272 280 end
273 281
274 282 if project
275 283 return user && user.allowed_to?(node.url, project)
276 284 else
277 285 # outside a project, all menu items allowed
278 286 return true
279 287 end
280 288 end
281 289 end
282 290
283 291 class << self
284 292 def map(menu_name)
285 293 @items ||= {}
286 294 mapper = Mapper.new(menu_name.to_sym, @items)
287 295 if block_given?
288 296 yield mapper
289 297 else
290 298 mapper
291 299 end
292 300 end
293 301
294 302 def items(menu_name)
295 303 @items[menu_name.to_sym] || Tree::TreeNode.new(:root, {})
296 304 end
297 305 end
298 306
299 307 class Mapper
300 308 def initialize(menu, items)
301 309 items[menu] ||= Tree::TreeNode.new(:root, {})
302 310 @menu = menu
303 311 @menu_items = items[menu]
304 312 end
305 313
306 314 @@last_items_count = Hash.new {|h,k| h[k] = 0}
307 315
308 316 # Adds an item at the end of the menu. Available options:
309 317 # * param: the parameter name that is used for the project id (default is :id)
310 318 # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true
311 319 # * caption that can be:
312 320 # * a localized string Symbol
313 321 # * a String
314 322 # * a Proc that can take the project as argument
315 323 # * before, after: specify where the menu item should be inserted (eg. :after => :activity)
316 324 # * parent: menu item will be added as a child of another named menu (eg. :parent => :issues)
317 325 # * children: a Proc that is called before rendering the item. The Proc should return an array of MenuItems, which will be added as children to this item.
318 326 # eg. :children => Proc.new {|project| [Redmine::MenuManager::MenuItem.new(...)] }
319 327 # * last: menu item will stay at the end (eg. :last => true)
320 328 # * html_options: a hash of html options that are passed to link_to
321 329 def push(name, url, options={})
322 330 options = options.dup
323 331
324 332 if options[:parent]
325 333 subtree = self.find(options[:parent])
326 334 if subtree
327 335 target_root = subtree
328 336 else
329 337 target_root = @menu_items.root
330 338 end
331 339
332 340 else
333 341 target_root = @menu_items.root
334 342 end
335 343
336 344 # menu item position
337 345 if first = options.delete(:first)
338 346 target_root.prepend(MenuItem.new(name, url, options))
339 347 elsif before = options.delete(:before)
340 348
341 349 if exists?(before)
342 350 target_root.add_at(MenuItem.new(name, url, options), position_of(before))
343 351 else
344 352 target_root.add(MenuItem.new(name, url, options))
345 353 end
346 354
347 355 elsif after = options.delete(:after)
348 356
349 357 if exists?(after)
350 358 target_root.add_at(MenuItem.new(name, url, options), position_of(after) + 1)
351 359 else
352 360 target_root.add(MenuItem.new(name, url, options))
353 361 end
354 362
355 elsif options.delete(:last)
363 elsif options[:last] # don't delete, needs to be stored
356 364 target_root.add_last(MenuItem.new(name, url, options))
357 365 else
358 366 target_root.add(MenuItem.new(name, url, options))
359 367 end
360 368 end
361 369
362 370 # Removes a menu item
363 371 def delete(name)
364 372 if found = self.find(name)
365 373 @menu_items.remove!(found)
366 374 end
367 375 end
368 376
369 377 # Checks if a menu item exists
370 378 def exists?(name)
371 379 @menu_items.any? {|node| node.name == name}
372 380 end
373 381
374 382 def find(name)
375 383 @menu_items.find {|node| node.name == name}
376 384 end
377 385
378 386 def position_of(name)
379 387 @menu_items.each do |node|
380 388 if node.name == name
381 389 return node.position
382 390 end
383 391 end
384 392 end
385 393 end
386 394
387 395 class MenuItem < Tree::TreeNode
388 396 include Redmine::I18n
389 attr_reader :name, :url, :param, :condition, :parent, :child_menus
397 attr_reader :name, :url, :param, :condition, :parent, :child_menus, :last
390 398
391 399 def initialize(name, url, options)
392 400 raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
393 401 raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
394 402 raise ArgumentError, "Cannot set the :parent to be the same as this item" if options[:parent] == name.to_sym
395 403 raise ArgumentError, "Invalid option :children for menu item '#{name}'" if options[:children] && !options[:children].respond_to?(:call)
396 404 @name = name
397 405 @url = url
398 406 @condition = options[:if]
399 407 @param = options[:param] || :id
400 408 @caption = options[:caption]
401 409 @html_options = options[:html] || {}
402 410 # Adds a unique class to each menu item based on its name
403 411 @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
404 412 @parent = options[:parent]
405 413 @child_menus = options[:children]
414 @last = options[:last] || false
406 415 super @name.to_sym
407 416 end
408 417
409 418 def caption(project=nil)
410 419 if @caption.is_a?(Proc)
411 420 c = @caption.call(project).to_s
412 421 c = @name.to_s.humanize if c.blank?
413 422 c
414 423 else
415 424 if @caption.nil?
416 425 l_or_humanize(name, :prefix => 'label_')
417 426 else
418 427 @caption.is_a?(Symbol) ? l(@caption) : @caption
419 428 end
420 429 end
421 430 end
422 431
423 432 def html_options(options={})
424 433 if options[:selected]
425 434 o = @html_options.dup
426 435 o[:class] += ' selected'
427 436 o
428 437 else
429 438 @html_options
430 439 end
431 440 end
432 441 end
433 442 end
434 443 end
@@ -1,166 +1,183
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../../../../test_helper'
19 19
20 class Redmine::MenuManager::MapperTest < Test::Unit::TestCase
20 class Redmine::MenuManager::MapperTest < ActiveSupport::TestCase
21 21 context "Mapper#initialize" do
22 22 should "be tested"
23 23 end
24 24
25 25 def test_push_onto_root
26 26 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
27 27 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
28 28
29 29 menu_mapper.exists?(:test_overview)
30 30 end
31 31
32 32 def test_push_onto_parent
33 33 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
34 34 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
35 35 menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview}
36 36
37 37 assert menu_mapper.exists?(:test_child)
38 38 assert_equal :test_child, menu_mapper.find(:test_child).name
39 39 end
40 40
41 41 def test_push_onto_grandparent
42 42 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
43 43 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
44 44 menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview}
45 45 menu_mapper.push :test_grandchild, { :controller => 'projects', :action => 'show'}, {:parent => :test_child}
46 46
47 47 assert menu_mapper.exists?(:test_grandchild)
48 48 grandchild = menu_mapper.find(:test_grandchild)
49 49 assert_equal :test_grandchild, grandchild.name
50 50 assert_equal :test_child, grandchild.parent.name
51 51 end
52 52
53 53 def test_push_first
54 54 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
55 55 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
56 56 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
57 57 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
58 58 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
59 59 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {:first => true}
60 60
61 61 root = menu_mapper.find(:root)
62 62 assert_equal 5, root.children.size
63 63 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
64 64 assert_not_nil root.children[position]
65 65 assert_equal name, root.children[position].name
66 66 end
67 67
68 68 end
69 69
70 70 def test_push_before
71 71 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
72 72 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
73 73 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
74 74 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
75 75 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
76 76 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {:before => :test_fourth}
77 77
78 78 root = menu_mapper.find(:root)
79 79 assert_equal 5, root.children.size
80 80 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
81 81 assert_not_nil root.children[position]
82 82 assert_equal name, root.children[position].name
83 83 end
84 84
85 85 end
86 86
87 87 def test_push_after
88 88 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
89 89 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
90 90 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
91 91 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
92 92 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
93 93 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {:after => :test_third}
94 94
95 95
96 96 root = menu_mapper.find(:root)
97 97 assert_equal 5, root.children.size
98 98 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
99 99 assert_not_nil root.children[position]
100 100 assert_equal name, root.children[position].name
101 101 end
102 102
103 103 end
104 104
105 105 def test_push_last
106 106 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
107 107 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
108 108 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
109 109 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
110 110 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {:last => true}
111 111 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
112 112
113 113 root = menu_mapper.find(:root)
114 114 assert_equal 5, root.children.size
115 115 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
116 116 assert_not_nil root.children[position]
117 117 assert_equal name, root.children[position].name
118 118 end
119 119
120 120 end
121 121
122 122 def test_exists_for_child_node
123 123 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
124 124 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
125 125 menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview }
126 126
127 127 assert menu_mapper.exists?(:test_child)
128 128 end
129 129
130 130 def test_exists_for_invalid_node
131 131 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
132 132 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
133 133
134 134 assert !menu_mapper.exists?(:nothing)
135 135 end
136 136
137 137 def test_find
138 138 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
139 139 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
140 140
141 141 item = menu_mapper.find(:test_overview)
142 142 assert_equal :test_overview, item.name
143 143 assert_equal({:controller => 'projects', :action => 'show'}, item.url)
144 144 end
145 145
146 146 def test_find_missing
147 147 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
148 148 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
149 149
150 150 item = menu_mapper.find(:nothing)
151 151 assert_equal nil, item
152 152 end
153 153
154 154 def test_delete
155 155 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
156 156 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
157 157 assert_not_nil menu_mapper.delete(:test_overview)
158 158
159 159 assert_nil menu_mapper.find(:test_overview)
160 160 end
161 161
162 162 def test_delete_missing
163 163 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
164 164 assert_nil menu_mapper.delete(:test_missing)
165 165 end
166
167 test 'deleting all items' do
168 # Exposed by deleting :last items
169 Redmine::MenuManager.map :test_menu do |menu|
170 menu.push :not_last, Redmine::Info.help_url
171 menu.push :administration, { :controller => 'projects', :action => 'show'}, {:last => true}
172 menu.push :help, Redmine::Info.help_url, :last => true
173 end
174
175 assert_nothing_raised do
176 Redmine::MenuManager.map :test_menu do |menu|
177 menu.delete(:administration)
178 menu.delete(:help)
179 menu.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
180 end
181 end
182 end
166 183 end
General Comments 0
You need to be logged in to leave comments. Login now