##// END OF EJS Templates
Introduce virtual MenuNodes (#15880)....
Jean-Philippe Lang -
r15119:53710d80fc88
parent child
Show More
@@ -1,455 +1,470
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 module Redmine
19 19 module MenuManager
20 20 class MenuError < StandardError #:nodoc:
21 21 end
22 22
23 23 module MenuController
24 24 def self.included(base)
25 25 base.extend(ClassMethods)
26 26 end
27 27
28 28 module ClassMethods
29 29 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
30 30 mattr_accessor :menu_items
31 31
32 32 # Set the menu item name for a controller or specific actions
33 33 # Examples:
34 34 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
35 35 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
36 36 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
37 37 #
38 38 # The default menu item name for a controller is controller_name by default
39 39 # Eg. the default menu item name for ProjectsController is :projects
40 40 def menu_item(id, options = {})
41 41 if actions = options[:only]
42 42 actions = [] << actions unless actions.is_a?(Array)
43 43 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
44 44 else
45 45 menu_items[controller_name.to_sym][:default] = id
46 46 end
47 47 end
48 48 end
49 49
50 50 def menu_items
51 51 self.class.menu_items
52 52 end
53 53
54 54 # Returns the menu item name according to the current action
55 55 def current_menu_item
56 56 @current_menu_item ||= menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
57 57 menu_items[controller_name.to_sym][:default]
58 58 end
59 59
60 60 # Redirects user to the menu item of the given project
61 61 # Returns false if user is not authorized
62 62 def redirect_to_project_menu_item(project, name)
63 63 item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name.to_s == name.to_s}
64 64 if item && item.allowed?(User.current, project)
65 65 redirect_to({item.param => project}.merge(item.url))
66 66 return true
67 67 end
68 68 false
69 69 end
70 70 end
71 71
72 72 module MenuHelper
73 73 # Returns the current menu item name
74 74 def current_menu_item
75 75 controller.current_menu_item
76 76 end
77 77
78 78 # Renders the application main menu
79 79 def render_main_menu(project)
80 80 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
81 81 end
82 82
83 83 def display_main_menu?(project)
84 84 menu_name = project && !project.new_record? ? :project_menu : :application_menu
85 85 Redmine::MenuManager.items(menu_name).children.present?
86 86 end
87 87
88 88 def render_menu(menu, project=nil)
89 89 links = []
90 90 menu_items_for(menu, project) do |node|
91 91 links << render_menu_node(node, project)
92 92 end
93 93 links.empty? ? nil : content_tag('ul', links.join.html_safe)
94 94 end
95 95
96 96 def render_menu_node(node, project=nil)
97 97 if node.children.present? || !node.child_menus.nil?
98 98 return render_menu_node_with_children(node, project)
99 99 else
100 100 caption, url, selected = extract_node_details(node, project)
101 101 return content_tag('li',
102 102 render_single_menu_node(node, caption, url, selected))
103 103 end
104 104 end
105 105
106 106 def render_menu_node_with_children(node, project=nil)
107 107 caption, url, selected = extract_node_details(node, project)
108 108
109 109 html = [].tap do |html|
110 110 html << '<li>'
111 111 # Parent
112 112 html << render_single_menu_node(node, caption, url, selected)
113 113
114 114 # Standard children
115 115 standard_children_list = "".html_safe.tap do |child_html|
116 116 node.children.each do |child|
117 117 child_html << render_menu_node(child, project) if allowed_node?(child, User.current, project)
118 118 end
119 119 end
120 120
121 121 html << content_tag(:ul, standard_children_list, :class => 'menu-children') unless standard_children_list.empty?
122 122
123 123 # Unattached children
124 124 unattached_children_list = render_unattached_children_menu(node, project)
125 125 html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank?
126 126
127 127 html << '</li>'
128 128 end
129 129 return html.join("\n").html_safe
130 130 end
131 131
132 132 # Returns a list of unattached children menu items
133 133 def render_unattached_children_menu(node, project)
134 134 return nil unless node.child_menus
135 135
136 136 "".html_safe.tap do |child_html|
137 137 unattached_children = node.child_menus.call(project)
138 138 # Tree nodes support #each so we need to do object detection
139 139 if unattached_children.is_a? Array
140 140 unattached_children.each do |child|
141 141 child_html << content_tag(:li, render_unattached_menu_item(child, project)) if allowed_node?(child, User.current, project)
142 142 end
143 143 else
144 144 raise MenuError, ":child_menus must be an array of MenuItems"
145 145 end
146 146 end
147 147 end
148 148
149 149 def render_single_menu_node(item, caption, url, selected)
150 link_to(h(caption), url, item.html_options(:selected => selected))
150 options = item.html_options(:selected => selected)
151
152 # virtual nodes are only there for their children to be displayed in the menu
153 # and should not do anything on click, except if otherwise defined elsewhere
154 if url.blank?
155 url = '#'
156 options.reverse_merge!(:onclick => 'return false;')
157 end
158 link_to(h(caption), url, options)
151 159 end
152 160
153 161 def render_unattached_menu_item(menu_item, project)
154 162 raise MenuError, ":child_menus must be an array of MenuItems" unless menu_item.is_a? MenuItem
155 163
156 164 if menu_item.allowed?(User.current, project)
157 165 link_to(menu_item.caption, menu_item.url, menu_item.html_options)
158 166 end
159 167 end
160 168
161 169 def menu_items_for(menu, project=nil)
162 170 items = []
163 171 Redmine::MenuManager.items(menu).root.children.each do |node|
164 172 if node.allowed?(User.current, project)
165 173 if block_given?
166 174 yield node
167 175 else
168 176 items << node # TODO: not used?
169 177 end
170 178 end
171 179 end
172 180 return block_given? ? nil : items
173 181 end
174 182
175 183 def extract_node_details(node, project=nil)
176 184 item = node
177 185 url = case item.url
178 186 when Hash
179 187 project.nil? ? item.url : {item.param => project}.merge(item.url)
180 188 when Symbol
181 189 if project
182 190 send(item.url, project)
183 191 else
184 192 send(item.url)
185 193 end
186 194 else
187 195 item.url
188 196 end
189 197 caption = item.caption(project)
190 198 return [caption, url, (current_menu_item == item.name)]
191 199 end
192 200
193 201 # See MenuItem#allowed?
194 202 def allowed_node?(node, user, project)
195 203 raise MenuError, ":child_menus must be an array of MenuItems" unless node.is_a? MenuItem
196 204 node.allowed?(user, project)
197 205 end
198 206 end
199 207
200 208 class << self
201 209 def map(menu_name)
202 210 @items ||= {}
203 211 mapper = Mapper.new(menu_name.to_sym, @items)
204 212 if block_given?
205 213 yield mapper
206 214 else
207 215 mapper
208 216 end
209 217 end
210 218
211 219 def items(menu_name)
212 220 @items[menu_name.to_sym] || MenuNode.new(:root, {})
213 221 end
214 222 end
215 223
216 224 class Mapper
217 225 attr_reader :menu, :menu_items
218 226
219 227 def initialize(menu, items)
220 228 items[menu] ||= MenuNode.new(:root, {})
221 229 @menu = menu
222 230 @menu_items = items[menu]
223 231 end
224 232
225 233 # Adds an item at the end of the menu. Available options:
226 234 # * param: the parameter name that is used for the project id (default is :id)
227 235 # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true
228 236 # * caption that can be:
229 237 # * a localized string Symbol
230 238 # * a String
231 239 # * a Proc that can take the project as argument
232 240 # * before, after: specify where the menu item should be inserted (eg. :after => :activity)
233 241 # * parent: menu item will be added as a child of another named menu (eg. :parent => :issues)
234 242 # * 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.
235 243 # eg. :children => Proc.new {|project| [Redmine::MenuManager::MenuItem.new(...)] }
236 244 # * last: menu item will stay at the end (eg. :last => true)
237 245 # * html_options: a hash of html options that are passed to link_to
238 246 def push(name, url, options={})
239 247 options = options.dup
240 248
241 249 if options[:parent]
242 250 subtree = self.find(options[:parent])
243 251 if subtree
244 252 target_root = subtree
245 253 else
246 254 target_root = @menu_items.root
247 255 end
248 256
249 257 else
250 258 target_root = @menu_items.root
251 259 end
252 260
253 261 # menu item position
254 262 if first = options.delete(:first)
255 263 target_root.prepend(MenuItem.new(name, url, options))
256 264 elsif before = options.delete(:before)
257 265
258 266 if exists?(before)
259 267 target_root.add_at(MenuItem.new(name, url, options), position_of(before))
260 268 else
261 269 target_root.add(MenuItem.new(name, url, options))
262 270 end
263 271
264 272 elsif after = options.delete(:after)
265 273
266 274 if exists?(after)
267 275 target_root.add_at(MenuItem.new(name, url, options), position_of(after) + 1)
268 276 else
269 277 target_root.add(MenuItem.new(name, url, options))
270 278 end
271 279
272 280 elsif options[:last] # don't delete, needs to be stored
273 281 target_root.add_last(MenuItem.new(name, url, options))
274 282 else
275 283 target_root.add(MenuItem.new(name, url, options))
276 284 end
277 285 end
278 286
279 287 # Removes a menu item
280 288 def delete(name)
281 289 if found = self.find(name)
282 290 @menu_items.remove!(found)
283 291 end
284 292 end
285 293
286 294 # Checks if a menu item exists
287 295 def exists?(name)
288 296 @menu_items.any? {|node| node.name == name}
289 297 end
290 298
291 299 def find(name)
292 300 @menu_items.find {|node| node.name == name}
293 301 end
294 302
295 303 def position_of(name)
296 304 @menu_items.each do |node|
297 305 if node.name == name
298 306 return node.position
299 307 end
300 308 end
301 309 end
302 310 end
303 311
304 312 class MenuNode
305 313 include Enumerable
306 314 attr_accessor :parent
307 315 attr_reader :last_items_count, :name
308 316
309 317 def initialize(name, content = nil)
310 318 @name = name
311 319 @children = []
312 320 @last_items_count = 0
313 321 end
314 322
315 323 def children
316 324 if block_given?
317 325 @children.each {|child| yield child}
318 326 else
319 327 @children
320 328 end
321 329 end
322 330
323 331 # Returns the number of descendants + 1
324 332 def size
325 333 @children.inject(1) {|sum, node| sum + node.size}
326 334 end
327 335
328 336 def each &block
329 337 yield self
330 338 children { |child| child.each(&block) }
331 339 end
332 340
333 341 # Adds a child at first position
334 342 def prepend(child)
335 343 add_at(child, 0)
336 344 end
337 345
338 346 # Adds a child at given position
339 347 def add_at(child, position)
340 348 raise "Child already added" if find {|node| node.name == child.name}
341 349
342 350 @children = @children.insert(position, child)
343 351 child.parent = self
344 352 child
345 353 end
346 354
347 355 # Adds a child as last child
348 356 def add_last(child)
349 357 add_at(child, -1)
350 358 @last_items_count += 1
351 359 child
352 360 end
353 361
354 362 # Adds a child
355 363 def add(child)
356 364 position = @children.size - @last_items_count
357 365 add_at(child, position)
358 366 end
359 367 alias :<< :add
360 368
361 369 # Removes a child
362 370 def remove!(child)
363 371 @children.delete(child)
364 372 @last_items_count -= +1 if child && child.last
365 373 child.parent = nil
366 374 child
367 375 end
368 376
369 377 # Returns the position for this node in it's parent
370 378 def position
371 379 self.parent.children.index(self)
372 380 end
373 381
374 382 # Returns the root for this node
375 383 def root
376 384 root = self
377 385 root = root.parent while root.parent
378 386 root
379 387 end
380 388 end
381 389
382 390 class MenuItem < MenuNode
383 391 include Redmine::I18n
384 392 attr_reader :name, :url, :param, :condition, :parent, :child_menus, :last, :permission
385 393
386 394 def initialize(name, url, options={})
387 395 raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
388 396 raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
389 397 raise ArgumentError, "Cannot set the :parent to be the same as this item" if options[:parent] == name.to_sym
390 398 raise ArgumentError, "Invalid option :children for menu item '#{name}'" if options[:children] && !options[:children].respond_to?(:call)
391 399 @name = name
392 400 @url = url
393 401 @condition = options[:if]
394 402 @permission = options[:permission]
395 403 @permission ||= false if options.key?(:permission)
396 404 @param = options[:param] || :id
397 405 @caption = options[:caption]
398 406 @html_options = options[:html] || {}
399 407 # Adds a unique class to each menu item based on its name
400 408 @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
401 409 @parent = options[:parent]
402 410 @child_menus = options[:children]
403 411 @last = options[:last] || false
404 412 super @name.to_sym
405 413 end
406 414
407 415 def caption(project=nil)
408 416 if @caption.is_a?(Proc)
409 417 c = @caption.call(project).to_s
410 418 c = @name.to_s.humanize if c.blank?
411 419 c
412 420 else
413 421 if @caption.nil?
414 422 l_or_humanize(name, :prefix => 'label_')
415 423 else
416 424 @caption.is_a?(Symbol) ? l(@caption) : @caption
417 425 end
418 426 end
419 427 end
420 428
421 429 def html_options(options={})
422 430 if options[:selected]
423 431 o = @html_options.dup
424 432 o[:class] += ' selected'
425 433 o
426 434 else
427 435 @html_options
428 436 end
429 437 end
430 438
431 439 # Checks if a user is allowed to access the menu item by:
432 440 #
433 441 # * Checking the permission or the url target (project only)
434 442 # * Checking the conditions of the item
435 443 def allowed?(user, project)
436 if user && project
444 if url.blank?
445 # this is a virtual node that is only there for its children to be diplayed in the menu
446 # it is considered an allowed node if at least one of the children is allowed
447 all_children = children
448 all_children += child_menus.call(project) if child_menus
449 return true if all_children.detect{|child| child.allowed?(user, project) }
450 return false
451 elsif user && project
437 452 if permission
438 453 unless user.allowed_to?(permission, project)
439 454 return false
440 455 end
441 456 elsif permission.nil? && url.is_a?(Hash)
442 457 unless user.allowed_to?(url, project)
443 458 return false
444 459 end
445 460 end
446 461 end
447 462 if condition && !condition.call(project)
448 463 # Condition that doesn't pass
449 464 return false
450 465 end
451 466 return true
452 467 end
453 468 end
454 469 end
455 470 end
@@ -1,306 +1,341
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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.expand_path('../../../../../test_helper', __FILE__)
19 19
20 20 class Redmine::MenuManager::MenuHelperTest < ActionView::TestCase
21 21
22 22 include Redmine::MenuManager::MenuHelper
23 23 include ERB::Util
24 24 fixtures :users, :members, :projects, :enabled_modules, :roles, :member_roles
25 25
26 26 def setup
27 27 setup_with_controller
28 28 # Stub the current menu item in the controller
29 29 def current_menu_item
30 30 :index
31 31 end
32 32 end
33 33
34 34 def test_render_single_menu_node
35 35 node = Redmine::MenuManager::MenuItem.new(:testing, '/test', { })
36 36 @output_buffer = render_single_menu_node(node, 'This is a test', node.url, false)
37 37
38 38 assert_select("a.testing", "This is a test")
39 39 end
40 40
41 41 def test_render_menu_node
42 42 single_node = Redmine::MenuManager::MenuItem.new(:single_node, '/test', { })
43 43 @output_buffer = render_menu_node(single_node, nil)
44 44
45 45 assert_select("li") do
46 46 assert_select("a.single-node", "Single node")
47 47 end
48 48 end
49 49
50 50 def test_render_menu_node_with_symbol_as_url
51 51 node = Redmine::MenuManager::MenuItem.new(:testing, :issues_path)
52 52 @output_buffer = render_menu_node(node, nil)
53 53
54 54 assert_select 'a[href="/issues"]', "Testing"
55 55 end
56 56
57 57 def test_render_menu_node_with_symbol_as_url_and_project
58 58 node = Redmine::MenuManager::MenuItem.new(:testing, :project_issues_path)
59 59 @output_buffer = render_menu_node(node, Project.find(1))
60 60
61 61 assert_select 'a[href="/projects/ecookbook/issues"]', "Testing"
62 62 end
63 63
64 64 def test_render_menu_node_with_nested_items
65 65 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, '/test', { })
66 66 parent_node << Redmine::MenuManager::MenuItem.new(:child_one_node, '/test', { })
67 67 parent_node << Redmine::MenuManager::MenuItem.new(:child_two_node, '/test', { })
68 68 parent_node <<
69 69 Redmine::MenuManager::MenuItem.new(:child_three_node, '/test', { }) <<
70 70 Redmine::MenuManager::MenuItem.new(:child_three_inner_node, '/test', { })
71 71
72 72 @output_buffer = render_menu_node(parent_node, nil)
73 73
74 74 assert_select("li") do
75 75 assert_select("a.parent-node", "Parent node")
76 76 assert_select("ul") do
77 77 assert_select("li a.child-one-node", "Child one node")
78 78 assert_select("li a.child-two-node", "Child two node")
79 79 assert_select("li") do
80 80 assert_select("a.child-three-node", "Child three node")
81 81 assert_select("ul") do
82 82 assert_select("li a.child-three-inner-node", "Child three inner node")
83 83 end
84 84 end
85 85 end
86 86 end
87 87
88 88 end
89 89
90 90 def test_render_menu_node_with_children
91 91 User.current = User.find(2)
92 92
93 93 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node,
94 94 '/test',
95 95 {
96 96 :children => Proc.new {|p|
97 97 children = []
98 98 3.times do |time|
99 99 children << Redmine::MenuManager::MenuItem.new("test_child_#{time}",
100 100 {:controller => 'issues', :action => 'index'},
101 101 {})
102 102 end
103 103 children
104 104 }
105 105 })
106 106 @output_buffer = render_menu_node(parent_node, Project.find(1))
107 107
108 108 assert_select("li") do
109 109 assert_select("a.parent-node", "Parent node")
110 110 assert_select("ul") do
111 111 assert_select("li a.test-child-0", "Test child 0")
112 112 assert_select("li a.test-child-1", "Test child 1")
113 113 assert_select("li a.test-child-2", "Test child 2")
114 114 end
115 115 end
116 116 end
117 117
118 118 def test_render_menu_node_with_nested_items_and_children
119 119 User.current = User.find(2)
120 120
121 121 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node,
122 122 {:controller => 'issues', :action => 'index'},
123 123 {
124 124 :children => Proc.new {|p|
125 125 children = []
126 126 3.times do |time|
127 127 children << Redmine::MenuManager::MenuItem.new("test_child_#{time}", {:controller => 'issues', :action => 'index'}, {})
128 128 end
129 129 children
130 130 }
131 131 })
132 132
133 133 parent_node << Redmine::MenuManager::MenuItem.new(:child_node,
134 134 {:controller => 'issues', :action => 'index'},
135 135 {
136 136 :children => Proc.new {|p|
137 137 children = []
138 138 6.times do |time|
139 139 children << Redmine::MenuManager::MenuItem.new("test_dynamic_child_#{time}", {:controller => 'issues', :action => 'index'}, {})
140 140 end
141 141 children
142 142 }
143 143 })
144 144
145 145 @output_buffer = render_menu_node(parent_node, Project.find(1))
146 146
147 147 assert_select("li") do
148 148 assert_select("a.parent-node", "Parent node")
149 149 assert_select("ul") do
150 150 assert_select("li a.child-node", "Child node")
151 151 assert_select("ul") do
152 152 assert_select("li a.test-dynamic-child-0", "Test dynamic child 0")
153 153 assert_select("li a.test-dynamic-child-1", "Test dynamic child 1")
154 154 assert_select("li a.test-dynamic-child-2", "Test dynamic child 2")
155 155 assert_select("li a.test-dynamic-child-3", "Test dynamic child 3")
156 156 assert_select("li a.test-dynamic-child-4", "Test dynamic child 4")
157 157 assert_select("li a.test-dynamic-child-5", "Test dynamic child 5")
158 158 end
159 159 assert_select("li a.test-child-0", "Test child 0")
160 160 assert_select("li a.test-child-1", "Test child 1")
161 161 assert_select("li a.test-child-2", "Test child 2")
162 162 end
163 163 end
164 164 end
165 165
166 166 def test_render_menu_node_with_allowed_and_unallowed_unattached_children
167 167 User.current = User.find(2)
168 168
169 169 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node,
170 170 {:controller => 'issues', :action => 'index'},
171 171 {
172 172 :children => Proc.new {|p|
173 173 [
174 174 Redmine::MenuManager::MenuItem.new("test_child_allowed", {:controller => 'issues', :action => 'index'}, {}),
175 175 Redmine::MenuManager::MenuItem.new("test_child_unallowed", {:controller => 'issues', :action => 'unallowed'}, {}),
176 176 ]
177 177 }
178 178 })
179 179
180 180 @output_buffer = render_menu_node(parent_node, Project.find(1))
181 181
182 182 assert_select("li") do
183 183 assert_select("a.parent-node", "Parent node")
184 184 assert_select("ul.menu-children.unattached") do
185 185 assert_select("li a.test-child-allowed", "Test child allowed")
186 186 assert_select("li a.test-child-unallowed", false)
187 187 end
188 188 end
189 189 end
190 190
191 191 def test_render_menu_node_with_allowed_and_unallowed_standard_children
192 192 User.current = User.find(6)
193 193
194 194 Redmine::MenuManager.map :some_menu do |menu|
195 195 menu.push(:parent_node, {:controller => 'issues', :action => 'index'}, { })
196 196 menu.push(:test_child_allowed, {:controller => 'issues', :action => 'index'}, {:parent => :parent_node})
197 197 menu.push(:test_child_unallowed, {:controller => 'issues', :action => 'new'}, {:parent => :parent_node})
198 198 end
199 199
200 200 @output_buffer = render_menu(:some_menu, Project.find(1))
201 201
202 202 assert_select("li") do
203 203 assert_select("a.parent-node", "Parent node")
204 204 assert_select("ul.menu-children.unattached", false)
205 205 assert_select("ul.menu-children") do
206 206 assert_select("li a.test-child-allowed", "Test child allowed")
207 207 assert_select("li a.test-child-unallowed", false)
208 208 end
209 209 end
210 210 end
211
212 def test_render_empty_virtual_menu_node_with_children
213
214 # only empty item with no click target
215 Redmine::MenuManager.map :menu1 do |menu|
216 menu.push(:parent_node, nil, { })
217 end
218
219 # parent with unallowed unattached child
220 Redmine::MenuManager.map :menu2 do |menu|
221 menu.push(:parent_node, nil, {:children => Proc.new {|p|
222 [Redmine::MenuManager::MenuItem.new("test_child_unallowed", {:controller => 'issues', :action => 'new'}, {})]
223 } })
224 end
225
226 # parent with unallowed standard child
227 Redmine::MenuManager.map :menu3 do |menu|
228 menu.push(:parent_node, nil, {})
229 menu.push(:test_child_unallowed, {:controller =>'issues', :action => 'new'}, {:parent => :parent_node})
230 end
231
232 # should not be displayed to anonymous
233 User.current = User.find(6)
234 assert_nil render_menu(:menu1, Project.find(1))
235 assert_nil render_menu(:menu2, Project.find(1))
236 assert_nil render_menu(:menu3, Project.find(1))
237
238 # should be displayed to an admin
239 User.current = User.find(1)
240 @output_buffer = render_menu(:menu2, Project.find(1))
241 assert_select("ul li a.parent-node", "Parent node")
242 @output_buffer = render_menu(:menu3, Project.find(1))
243 assert_select("ul li a.parent-node", "Parent node")
244
245 end
211 246
212 247 def test_render_menu_node_with_children_without_an_array
213 248 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node,
214 249 '/test',
215 250 {
216 251 :children => Proc.new {|p| Redmine::MenuManager::MenuItem.new("test_child", "/testing", {})}
217 252 })
218 253
219 254 assert_raises Redmine::MenuManager::MenuError, ":children must be an array of MenuItems" do
220 255 @output_buffer = render_menu_node(parent_node, Project.find(1))
221 256 end
222 257 end
223 258
224 259 def test_render_menu_node_with_incorrect_children
225 260 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node,
226 261 '/test',
227 262 {
228 263 :children => Proc.new {|p| ["a string"] }
229 264 })
230 265
231 266 assert_raises Redmine::MenuManager::MenuError, ":children must be an array of MenuItems" do
232 267 @output_buffer = render_menu_node(parent_node, Project.find(1))
233 268 end
234 269
235 270 end
236 271
237 272 def test_menu_items_for_should_yield_all_items_if_passed_a_block
238 273 menu_name = :test_menu_items_for_should_yield_all_items_if_passed_a_block
239 274 Redmine::MenuManager.map menu_name do |menu|
240 275 menu.push(:a_menu, '/', { })
241 276 menu.push(:a_menu_2, '/', { })
242 277 menu.push(:a_menu_3, '/', { })
243 278 end
244 279
245 280 items_yielded = []
246 281 menu_items_for(menu_name) do |item|
247 282 items_yielded << item
248 283 end
249 284
250 285 assert_equal 3, items_yielded.size
251 286 end
252 287
253 288 def test_menu_items_for_should_return_all_items
254 289 menu_name = :test_menu_items_for_should_return_all_items
255 290 Redmine::MenuManager.map menu_name do |menu|
256 291 menu.push(:a_menu, '/', { })
257 292 menu.push(:a_menu_2, '/', { })
258 293 menu.push(:a_menu_3, '/', { })
259 294 end
260 295
261 296 items = menu_items_for(menu_name)
262 297 assert_equal 3, items.size
263 298 end
264 299
265 300 def test_menu_items_for_should_skip_unallowed_items_on_a_project
266 301 menu_name = :test_menu_items_for_should_skip_unallowed_items_on_a_project
267 302 Redmine::MenuManager.map menu_name do |menu|
268 303 menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { })
269 304 menu.push(:a_menu_2, {:controller => 'issues', :action => 'index' }, { })
270 305 menu.push(:unallowed, {:controller => 'issues', :action => 'unallowed' }, { })
271 306 end
272 307
273 308 User.current = User.find(2)
274 309
275 310 items = menu_items_for(menu_name, Project.find(1))
276 311 assert_equal 2, items.size
277 312 end
278 313
279 314 def test_menu_items_for_should_skip_items_that_fail_the_permission
280 315 menu_name = :test_menu_items_for_should_skip_items_that_fail_the_permission
281 316 Redmine::MenuManager.map menu_name do |menu|
282 317 menu.push(:a_menu, :project_issues_path)
283 318 menu.push(:unallowed, :project_issues_path, :permission => :unallowed)
284 319 end
285 320
286 321 User.current = User.find(2)
287 322
288 323 items = menu_items_for(menu_name, Project.find(1))
289 324 assert_equal 1, items.size
290 325 end
291 326
292 327 def test_menu_items_for_should_skip_items_that_fail_the_conditions
293 328 menu_name = :test_menu_items_for_should_skip_items_that_fail_the_conditions
294 329 Redmine::MenuManager.map menu_name do |menu|
295 330 menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { })
296 331 menu.push(:unallowed,
297 332 {:controller => 'issues', :action => 'index' },
298 333 { :if => Proc.new { false }})
299 334 end
300 335
301 336 User.current = User.find(2)
302 337
303 338 items = menu_items_for(menu_name, Project.find(1))
304 339 assert_equal 1, items.size
305 340 end
306 341 end
General Comments 0
You need to be logged in to leave comments. Login now