Browse files

added tests and updated README

  • Loading branch information...
1 parent 700be69 commit ba4dd7692a079ea4eac9e80112eed97d0031686f @rpheath committed Oct 26, 2009
Showing with 311 additions and 6 deletions.
  1. +110 −1 README.textile
  2. +131 −4 test/navigation_test.rb
  3. +70 −1 test/test_helper.rb
View
111 README.textile
@@ -1,3 +1,112 @@
h1. Navigation Plugin for Rails
-This is an experiment with a different way to create navigation menus in Rails. It supports navigation at the controller level, as well as the action level. Also supports nested navigation menus.
+This is an experiment with a different way to create navigation menus in Rails. It supports navigation at the controller level, as well as the action level (nested or non-nested).
+
+h2. Initialization
+
+Probably the most fundamental difference with this plugin and most navigation plugins is this one requires the menus to be pre-defined before rendering. Here's an example of a typical menu (in @config/initializers/navigation.rb@):
+
+<pre><code>RPH::Navigation::Builder.config do |navigation|
+ navigation.define :primary do |menu|
+ menu.item :home, :text => 'Welcome'
+ menu.item :about, :path => :about_us_path
+ menu.item :contact, :text => 'Contact Us'
+ end
+end
+</code></pre>
+
+A caveat to this approach is that you don't have access to the named route helpers in an initializer, so you have to pass them as either a symbol or a string and they'll be eval'd at the right time. If you don't pass a route, it will try to do @"#{menu_item_name}_path"@. So the "home" item in the above example would get a path of "home_path" since there was no @:path@ option. And if "home_path" doesn't exist, the fallback is "root_path".
+
+h2. Rendering the Menu
+
+Once you have your menu(s) defined, it's pretty easy to render your menu:
+
+<pre><code><%= navigation :primary %></code></pre>
+
+You just have to pass the key of the name you gave it when it was defined. This is good for a number of reasons, but an added bonus is the ability to define and render different menus depending on who is logged in, for example.
+
+<pre><code><%= navigation current_user.menu %></code></pre>
+
+All of the logic related to roles/permissions could be abstracted into a @menu()@ method. Or if you're funny about sticking that logic in a model, you could call a helper or something. But you get the idea: key-based menu identification.
+
+h2. Tips & Advanced Features
+
+There are some additional things that you may want to know about this plugin, so keep reading if you're not satisfied yet.
+
+h3. Passing a controller instance...
+
+When I'm writing code to determine the "current tab" stuff, I basically just want this: when any action inside of the @HomeController@ gets rendered, highlight the "home" tab as the current. So why not allow the ability to pass the controller instance in the menu definition?
+
+<pre><code>RPH::Navigation::Builder.config do |navigation|
+ navigation.define :primary do |menu|
+ menu.item HomeController, :text => 'Welcome'
+ menu.item AboutController, :path => :about_us_path
+ menu.item ContactController, :text => 'Contact Us'
+ end
+end
+</code></pre>
+
+No more guessing games and no more @current_tab :home@ in your controllers.
+
+h3. Rule-based navigation menus...
+
+Sometimes you only want to show a menu based on a some condition. It happens. Just pass an @:if@ condition to the menu definition:
+
+<pre><code>RPH::Navigation::Builder.config do |navigation|
+ navigation.define :primary, :if => Proc.new { |view| view.allowed_to_show_menu? } do |menu|
+ # ...
+ end
+end
+</code></pre>
+
+You will automatically be handed a reference to the template/view so you can call any method that @ActionView@ is aware of (Note: if you need the controller, just use @view.controller@).
+
+h3. Rule-based navigation menu items...
+
+And sometimes you always want the menu present, but one or two of the tabs should only show up for certain reasons. Well, the @:if@ option works exactly the same for menu items:
+
+<pre><code>RPH::Navigation::Builder.config do |navigation|
+ navigation.define :primary do |menu|
+ menu.item :home, :text => 'Welcome'
+ menu.item :about, :path => :about_us_path
+ menu.item :admin, :if => Proc.new { |view| view.logged_in_as_admin? }
+ end
+end
+</code></pre>
+
+h3. Sub-menu navigation...
+
+As we all know, a controller is made up of actions. And sometimes you want to not only provide navigation around your controllers, but also around the actions within a controller. Well, it's a pretty simple concept and now has a pretty simple solution:
+
+<pre><code>RPH::Navigation::Builder.config do |navigation|
+ navigation.define :primary do |menu|
+ menu.item :home, :text => 'Welcome' do |sub_menu|
+ sub_menu.item :index, :text => 'Dashboard', :path => :home_path
+ sub_menu.item :friends, :path => :friends_path
+ end
+ menu.item :about, :path => :about_us_path
+ menu.item :contact, :text => 'Contact Us'
+ end
+end
+</code></pre>
+
+If you pass a block to any of the menu items, the plugin will automatically know that this is a sub-menu and will render a completely new menu underneath of the parent menu item. And it's "current tab" will be based on the @action_name@ instead of the @controller_name@. Remember, this is if you want a *nested* action-level menu.
+
+h3. Action-level navigation...
+
+If you want to build a separate menu for the actions of a controller, but you _don't_ want to nest it under a parent menu item, you can tell the builder that during the definition by passing @action_menu => true@:
+
+<pre><code>RPH::Navigation::Builder.config do |navigation|
+ navigation.define :users, :action_menu => true do |menu|
+ # ...
+ end
+end
+</code></pre>
+
+h3. That's all!
+
+Feedback is welcome, and as always, feel free to fork and improve :-)
+
+h2. License
+
+Copyright (c) 2009 Ryan Heath MIT
View
135 test/navigation_test.rb
@@ -1,8 +1,135 @@
require File.join(File.dirname(__FILE__), 'test_helper')
+Nav = Class.new(RPH::Navigation::Navigator)
+
class NavigationTest < ActiveSupport::TestCase
- # Replace this with your real tests.
- test "the truth" do
- assert true
+ def setup
+ @view = ActionView::Base.new
+ @basic = Nav.new(:basic, { :view => @view })
+ @menu_with_proc = Nav.new(:menu_with_proc, { :view => @view })
+ @custom_menu = Nav.new(:basic, { :view => @view, :class => "navigator" })
+ @menu_with_submenu = Nav.new(:menu_with_submenu, { :view => @view })
+ end
+
+ context "default behavior" do
+ test "should be basic navigation" do
+ assert_equal @basic.name, "basic"
+ end
+
+ test "should have access to the view" do
+ assert_equal @basic.view, @view
+ end
+
+ test "should have a 'navigation' css class" do
+ assert_equal @basic.css_class, "navigation"
+ end
+
+ test "should be 'allowed' if no Proc is specified" do
+ assert @basic.allowed?
+ end
+
+ test "menu items should be wrapped in an OrderedHash" do
+ assert @basic.send(:items).is_a?(ActiveSupport::OrderedHash)
+ end
+
+ test "should have appropriate items (and in order)" do
+ items = ActiveSupport::OrderedHash.new
+ %w(home features events).each do |menu_item|
+ items[menu_item] = {}
+ end
+
+ assert_equal @basic.send(:items), items
+ end
+ end
+
+ context "custom menu options" do
+ test "should have a custom CSS class" do
+ assert_equal @custom_menu.css_class, "navigator"
+ end
+ end
+
+ context "menu validation" do
+ test "should be a valid menu" do
+ assert @basic.send(:valid?, :basic)
+ end
+
+ test "should not be a valid menu" do
+ assert !@basic.send(:valid?, :invalid_menu)
+ end
+ end
+
+ context "menu with Proc dependency" do
+ test "should be a valid menu" do
+ assert @menu_with_proc.send(:valid?, :menu_with_proc)
+ end
+
+ test "should show the menu" do
+ @view.instance_eval do
+ def show_menu?
+ true
+ end
+ end
+
+ assert @menu_with_proc.allowed?
+ end
+
+ test "should NOT show the menu" do
+ @view.instance_eval do
+ def show_menu?
+ false
+ end
+ end
+
+ assert !@menu_with_proc.allowed?
+ end
+ end
+
+ context "disecting menu items" do
+ test "should return an array with 3 items" do
+ result = @basic.send(:disect, 'home', {})
+ assert result.is_a?(Array)
+ assert_equal 3, result.size
+ end
+
+ test "should return a disected menu item" do
+ result = @basic.send(:disect, 'home', {})
+ assert_equal ["Home", "/home", {}], result
+ end
+
+ test "should return a disected menu item with custom path and options" do
+ result = @basic.send(:disect, 'home',
+ { :path => "custom_home_path", :class => 'custom', :text => "Custom Home" })
+ assert_equal ["Custom Home", "/path/to/home", { "class" => "custom" }], result
+ end
+
+ test "should return a disected menu item disregarding SUBMENU and :if options" do
+ result = @basic.send(:disect, 'home',
+ { RPH::Navigation::SUBMENU => "whatever", :if => Proc.new {} })
+ assert_equal ["Home", "/home", {}], result
+ end
+ end
+
+ context "constructing the HTML" do
+ test "should return the proper HTML links" do
+ @view.controller = HomeController.new
+ assert_equal @basic.links, [
+ "<li class=\"current\"><a href=\"/home\">Home</a></li>",
+ "<li class=\"\"><a href=\"/features\">Features</a></li>",
+ "<li class=\"\"><a href=\"/events\">Events</a></li>"
+ ]
+ end
+
+ test "should be current tab on the features link" do
+ @view.controller = FeaturesController.new
+ assert_equal @basic.links[1], "<li class=\"current\"><a href=\"/features\">Features</a></li>"
+ end
+
+ test "should return a nested submenu under the Home link" do
+ @view.controller = HomeController.new
+ assert_equal @menu_with_submenu.links, [
+ "<li class=\"current\"><a href=\"/home\">Home</a><ul class=\"sub-navigation\"><li class=\"current\"><a href=\"/\">Index</a></li></ul></li>",
+ "<li class=\"\"><a href=\"/features\">Features</a></li>"
+ ]
+ end
end
-end
+end
View
71 test/test_helper.rb
@@ -1,3 +1,72 @@
require 'rubygems'
+require 'test/unit'
+require 'action_view'
require 'active_support'
-require 'active_support/test_case'
+require 'active_support/test_case'
+require 'context'
+require File.join(File.dirname(__FILE__), '..', 'lib', 'navigation')
+
+class HomeController
+ def controller_name
+ 'home'
+ end
+
+ def action_name
+ 'index'
+ end
+end
+
+class FeaturesController
+ def controller_name
+ 'features'
+ end
+end
+
+class ActionView::Base
+ def root_path
+ '/'
+ end
+
+ def home_path
+ '/home'
+ end
+
+ def custom_home_path
+ '/path/to/home'
+ end
+
+ def features_path
+ '/features'
+ end
+
+ def events_path
+ '/events'
+ end
+
+ def show_menu?
+ true
+ end
+end
+
+RPH::Navigation::Builder.config do |navigation|
+ navigation.define :basic do |menu|
+ menu.item :home
+ menu.item :features
+ menu.item :events
+ end
+end
+
+RPH::Navigation::Builder.config do |navigation|
+ navigation.define :menu_with_proc, :if => Proc.new { |view| view.show_menu? } do |menu|
+ menu.item :home
+ end
+end
+
+RPH::Navigation::Builder.config do |navigation|
+ navigation.define :menu_with_submenu do |menu|
+ menu.item :home do |sub|
+ sub.item :index, :path => :root_path
+ end
+ menu.item :features
+ end
+end

0 comments on commit ba4dd76

Please sign in to comment.