navigatrix 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ module Navigatrix
2
+ module Rendering
3
+ class Context < Struct.new(:wrapped)
4
+ extend Forwardable
5
+ delegate :request => :wrapped
6
+
7
+ def current_path
8
+ request.env["PATH_INFO"]
9
+ end
10
+
11
+ def controller_name
12
+ attempt_delegation(:controller_name)
13
+ end
14
+
15
+ def action_name
16
+ attempt_delegation(:action_name)
17
+ end
18
+
19
+ private
20
+
21
+ def attempt_delegation(method)
22
+ wrapped.send(method) if wrapped.respond_to?(method)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ require "navigatrix/rendering/strategies/list"
2
+
3
+ module Navigatrix::Rendering::Strategies
4
+ module Bootstrap
5
+ class Navbar < List
6
+
7
+ private
8
+
9
+ def html_attributes
10
+ super.merge_attribute(:class, "nav")
11
+ end
12
+
13
+ def items
14
+ super.map { |item| (item.has_children? ? Item : List::Item).new(item, options) }
15
+ end
16
+
17
+ class Item < List::Item
18
+ private
19
+
20
+ def html_attributes
21
+ super.merge_attribute(:class, "dropdown")
22
+ end
23
+
24
+ def name
25
+ (super + dropdown_icon).html_safe
26
+ end
27
+
28
+ def nested_list
29
+ List.new(children, options).render if has_children?
30
+ end
31
+
32
+ def dropdown_icon
33
+ options[:dropdown_icon] || content_tag(:i, nil, :class => "icon-chevron-down icon-white")
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ module Navigatrix::Rendering::Strategies
2
+ module Bootstrap
3
+ class Tabs < List
4
+
5
+ private
6
+
7
+ def html_attributes
8
+ super.merge_attribute(:class, "nav nav-tabs")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,100 @@
1
+ require "action_view"
2
+
3
+ module Navigatrix::Rendering::Strategies
4
+ class List < Struct.new(:items, :options)
5
+ include ActionView::Helpers::TagHelper
6
+ include ActionView::Helpers::UrlHelper
7
+
8
+ def render
9
+ content_tag(:ul, render_items, html_attributes)
10
+ end
11
+
12
+ def render_items
13
+ items.map(&:render).join.html_safe
14
+ end
15
+
16
+ private
17
+
18
+ def items
19
+ super.map { |item| item_class.new(item, options) }
20
+ end
21
+
22
+ def item_class
23
+ Item
24
+ end
25
+
26
+ def html_attributes
27
+ HTMLAttributes.new(options[:html_attributes])
28
+ end
29
+
30
+ class Item < SimpleDelegator
31
+ include ActionView::Helpers::TagHelper
32
+ include ActionView::Helpers::UrlHelper
33
+
34
+ attr_reader :options
35
+
36
+ def initialize(item, options = {})
37
+ super(item)
38
+ @options = options
39
+ end
40
+
41
+ def render
42
+ content_tag(:li, content, html_attributes) if render?
43
+ end
44
+
45
+ private
46
+
47
+ def content
48
+ name_or_link + nested_list.to_s
49
+ end
50
+
51
+ def name_or_link
52
+ linked? ? link : unlinked_content
53
+ end
54
+
55
+ def nested_list
56
+ List.new(children, options).render if has_children?
57
+ end
58
+
59
+ def link
60
+ link_to(name, path)
61
+ end
62
+
63
+ def unlinked_content
64
+ name
65
+ end
66
+
67
+ def name
68
+ super.html_safe
69
+ end
70
+
71
+ def html_attributes
72
+ HTMLAttributes.new(super).merge_attribute(:class, html_class)
73
+ end
74
+
75
+ def html_class
76
+ active? ? active_class : inactive_class
77
+ end
78
+
79
+ def active_class
80
+ options[:active_class] || "active"
81
+ end
82
+
83
+ def inactive_class
84
+ options[:inactive_class]
85
+ end
86
+ end
87
+
88
+ class HTMLAttributes < HashWithIndifferentAccess
89
+ def merge_attribute(attribute, value)
90
+ tap do
91
+ self[attribute] = attribute_values(attribute).push(value).join(" ")
92
+ end
93
+ end
94
+
95
+ def attribute_values(attribute)
96
+ fetch(attribute, "").to_s.split(" ")
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,3 @@
1
+ module Navigatrix
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ module Navigatrix
2
+ module ViewHelpers
3
+ def render_navigation(configuration, options = {})
4
+ Navigatrix::Renderer.new(configuration, options.merge({:render_context => self})).render
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'navigatrix/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "navigatrix"
8
+ spec.version = Navigatrix::VERSION
9
+ spec.authors = ["Ben Eddy"]
10
+ spec.email = ["bae@foraker.com"]
11
+ spec.description = %q{Navigation Generation}
12
+ spec.summary = %q{Navigation generation for Rails and Sinatra}
13
+ spec.homepage = "https://github.com/foraker/navigatrix"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "i18n"
22
+ spec.add_dependency "activesupport"
23
+ spec.add_dependency "actionpack"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec", "~> 2.0"
28
+ spec.add_development_dependency "capybara"
29
+ end
@@ -0,0 +1,247 @@
1
+ require File.expand_path("../../lib/navigatrix/item", __FILE__)
2
+ require File.expand_path("../../lib/navigatrix/item_collection", __FILE__)
3
+ require File.expand_path("../../lib/navigatrix/configuration", __FILE__)
4
+
5
+ module Navigatrix
6
+ describe Item do
7
+ let(:context) { double({:current_path => "/"}) }
8
+
9
+ it "knows its name" do
10
+ Item.new("Item 1", {}, context).name.should == "Item 1"
11
+ end
12
+
13
+ describe "#path" do
14
+ it "returns a file paths" do
15
+ Item.new("Item 1", {"path" => "/a/standard/path"}).path.should == "/a/standard/path"
16
+ end
17
+
18
+ it "evaluates a Proc" do
19
+ context.stub(:path_method => "/a/path/method")
20
+ path_proc = Proc.new { |context| context.path_method }
21
+ item = Item.new("Item 1", {"path" => path_proc}, context)
22
+ item.path.should == "/a/path/method"
23
+ end
24
+ end
25
+
26
+ describe "#active?" do
27
+ it "is true when the navigation is unlinked" do
28
+ item = new_item({})
29
+ item.stub(:unlinked? => true)
30
+ item.should be_active
31
+ end
32
+
33
+ it "is false when the navigation is linked" do
34
+ item = new_item({})
35
+ item.stub(:unlinked? => false)
36
+ item.should_not be_active
37
+ end
38
+
39
+ context "a controller name is specified in the config" do
40
+ let(:config) { {"active_states" => {"controller" => "controller_1"}} }
41
+
42
+ it "is true if the specified controller is the current controller" do
43
+ context.stub(:controller_name => "controller_1", :action_name => "index")
44
+ new_item(config).should be_active
45
+ end
46
+
47
+ it "is true regardless of action name" do
48
+ context.stub(:controller_name => "controller_1", :action_name => "edit")
49
+ new_item(config).should be_active
50
+ end
51
+ end
52
+
53
+ context "a controller pattern is specified in the config" do
54
+ let(:config) { {"active_states" => {"controller" => /^users/}} }
55
+
56
+ it "is true if the specified controller is the current controller" do
57
+ context.stub(:controller_name => "users", :action_name => "index")
58
+ new_item(config).should be_active
59
+ end
60
+
61
+ it "is true if the specified controller matches the current controller" do
62
+ context.stub(:controller_name => "users/comments", :action_name => "index")
63
+ new_item(config).should be_active
64
+ end
65
+
66
+ it "is true regardless of action name" do
67
+ context.stub(:controller_name => "users", :action_name => "edit")
68
+ new_item(config).should be_active
69
+ end
70
+
71
+ it "is false if the name does not match" do
72
+ context.stub(:controller_name => "comments/users", :action_name => "edit")
73
+ new_item(config).should_not be_active
74
+ end
75
+ end
76
+
77
+ context "a controller and action are specified in the config" do
78
+ let(:config) do
79
+ {"active_states" => {"controller" => "controller_2", "actions" => "index"}}
80
+ end
81
+
82
+ it "is true if the specified controller/action combination is the current controller/action combination" do
83
+ context.stub(:controller_name => "controller_2", :action_name => "index")
84
+ new_item(config).should be_active
85
+ end
86
+
87
+ it "is false when the action name mismatches" do
88
+ context.stub(:controller_name => "controller_2", :action_name => "edit")
89
+ new_item(config).should_not be_active
90
+ end
91
+
92
+ it "is false when the controller name mismatches" do
93
+ context.stub(:controller_name => "controller_1", :action_name => "index")
94
+ new_item(config).should_not be_active
95
+ end
96
+ end
97
+
98
+ context "multiple active states are specified" do
99
+ let(:config) do
100
+ {"active_states" => [
101
+ {"controller" => "controller_3"},
102
+ {"controller" => "controller_4", "actions" => "show"},
103
+ ]}
104
+ end
105
+
106
+ it "is true if the first state is applicable" do
107
+ context.stub(:controller_name => "controller_3", :action_name => "index")
108
+ new_item(config).should be_active
109
+ end
110
+
111
+ it "is indifferent to action name in the first case" do
112
+ context.stub(:controller_name => "controller_3", :action_name => "show")
113
+ new_item(config).should be_active
114
+ end
115
+
116
+ it "is true if the second state is applicable" do
117
+ context.stub(:controller_name => "controller_4", :action_name => "show")
118
+ new_item(config).should be_active
119
+ end
120
+
121
+ it "is honors action name specifications" do
122
+ context.stub(:controller_name => "controller_4", :action_name => "edit")
123
+ new_item(config).should_not be_active
124
+ end
125
+
126
+ it "is false when neither apply" do
127
+ context.stub(:controller_name => "controller_5", :action_name => "edit")
128
+ new_item(config).should_not be_active
129
+ end
130
+ end
131
+ end
132
+
133
+ describe "#unlinked?" do
134
+ context "no unlinked states are configured" do
135
+ before { context.stub(:current_path => "/path_1") }
136
+
137
+ it "is true when the current_path is the same as the item path" do
138
+ item = new_item({})
139
+ item.stub(:path => "/path_1")
140
+ item.should be_unlinked
141
+ end
142
+
143
+ it "is false when the current_path is not the same the item path" do
144
+ item = new_item({})
145
+ item.stub(:path => "/path_2")
146
+ item.should_not be_unlinked
147
+ end
148
+ end
149
+
150
+ context "unlink states are configured" do
151
+ context "a controller is specified" do
152
+ let(:config) { {"unlinked_states" => {"controller" => "controller_1"}} }
153
+
154
+ it "is true if the specified controller is the current controller" do
155
+ context.stub(:controller_name => "controller_1", :action_name => "index")
156
+ new_item(config).should be_unlinked
157
+ end
158
+
159
+ it "is true regardless of action name" do
160
+ context.stub(:controller_name => "controller_1", :action_name => "edit")
161
+ new_item(config).should be_unlinked
162
+ end
163
+ end
164
+
165
+ context "a controller and action are specified config" do
166
+ let(:config) do
167
+ {"unlinked_states" => {"controller" => "controller_2", "actions" => "index"}}
168
+ end
169
+
170
+ it "is true if the specified controller/action combination is the current controller/action combination" do
171
+ context.stub(:controller_name => "controller_2", :action_name => "index")
172
+ new_item(config).should be_unlinked
173
+ end
174
+
175
+ it "is false when the action name mismatches" do
176
+ context.stub(:controller_name => "controller_2", :action_name => "edit")
177
+ new_item(config).should_not be_unlinked
178
+ end
179
+
180
+ it "is false when the controller name mismatches" do
181
+ context.stub(:controller_name => "controller_1", :action_name => "index")
182
+ new_item(config).should_not be_active
183
+ end
184
+ end
185
+
186
+ context "multiple active states are specified" do
187
+ let(:config) do
188
+ {"unlinked_states" => [
189
+ {"controller" => "controller_3"},
190
+ {"controller" => "controller_4", "actions" => "show"},
191
+ ]}
192
+ end
193
+
194
+ it "is true if the first state is applicable" do
195
+ context.stub(:controller_name => "controller_3", :action_name => "index")
196
+ new_item(config).should be_unlinked
197
+ end
198
+
199
+ it "is indifferent to action name in the first case" do
200
+ context.stub(:controller_name => "controller_3", :action_name => "show")
201
+ new_item(config).should be_unlinked
202
+ end
203
+
204
+ it "is true if the second state is applicable" do
205
+ context.stub(:controller_name => "controller_4", :action_name => "show")
206
+ new_item(config).should be_unlinked
207
+ end
208
+
209
+ it "is honors action name specifications" do
210
+ context.stub(:controller_name => "controller_4", :action_name => "edit")
211
+ new_item(config).should_not be_unlinked
212
+ end
213
+
214
+ it "is false when neither apply" do
215
+ context.stub(:controller_name => "controller_5", :action_name => "edit")
216
+ new_item(config).should_not be_unlinked
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ describe "#children" do
223
+ let(:item) { new_item({"children" => {"Child 1" => {}}}) }
224
+ let(:child) { item.children.first }
225
+
226
+ it "creates the correct number of children" do
227
+ item.children.count.should == 1
228
+ end
229
+
230
+ it "returns an array of items" do
231
+ child.should be_instance_of(Item)
232
+ end
233
+
234
+ it "maps its children to items" do
235
+ child.name.should == "Child 1"
236
+ end
237
+
238
+ it "can handle childless configurations" do
239
+ expect { new_item({}) }.to_not raise_error
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+ def new_item(config)
246
+ Navigatrix::Item.new("", config, context)
247
+ end