17 | # element.
18 | class Links < SimpleNavigation::Renderer::Base
19 | def render(item_container)
20 | div_content = item_container.items
21 | .map { |item| tag_for(item) }
22 | .join(join_with)
23 | content_tag :div, div_content, item_container.dom_attributes
24 | end
25 |
26 | protected
27 |
28 | def join_with
29 | @join_with ||= options[:join_with] || ''
30 | end
31 |
32 | def options_for(item)
33 | { method: item.method }.merge(item.html_options)
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/simple_navigation/renderer/list.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | module Renderer
3 | # Renders an ItemContainer as a
element and its containing items as
4 | # - elements.
5 | # It adds the 'selected' class to li element AND the link inside the li
6 | # element that is currently active.
7 | #
8 | # If the sub navigation should be included (based on the level and
9 | # expand_all options), it renders another
containing the sub navigation
10 | # inside the active - element.
11 | #
12 | # By default, the renderer sets the item's key as dom_id for the rendered
13 | #
- element unless the config option autogenerate_item_ids is
14 | # set to false.
15 | # The id can also be explicitely specified by setting the id in the
16 | # html-options of the 'item' method in the config/navigation.rb file.
17 | class List < SimpleNavigation::Renderer::Base
18 | def render(item_container)
19 | if skip_if_empty? && item_container.empty?
20 | ''
21 | else
22 | tag = options[:ordered] ? :ol : :ul
23 | content = list_content(item_container)
24 | content_tag(tag, content, item_container.dom_attributes)
25 | end
26 | end
27 |
28 | private
29 |
30 | def list_content(item_container)
31 | item_container.items.map { |item|
32 | li_options = item.html_options.except(:link)
33 | li_content = tag_for(item)
34 | if include_sub_navigation?(item)
35 | li_content << render_sub_navigation_for(item)
36 | end
37 | content_tag(:li, li_content, li_options)
38 | }.join
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/simple_navigation/renderer/text.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | module Renderer
3 | # Renders the 'chain' of selected navigation items as simple text items,
4 | # joined with an optional separator (similar to breadcrumbs, but without
5 | # markup).
6 | class Text < SimpleNavigation::Renderer::Base
7 | def render(item_container)
8 | list(item_container).compact.join(options[:join_with] || ' ')
9 | end
10 |
11 | private
12 |
13 | def list(item_container)
14 | item_container.items.keep_if(&:selected?).map do |item|
15 | [item.name(apply_generator: false)] +
16 | (include_sub_navigation?(item) ? list(item.sub_navigation) : [])
17 | end
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/simple_navigation/version.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | VERSION = '4.4.0'
3 | end
4 |
--------------------------------------------------------------------------------
/simple-navigation.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'simple_navigation/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = 'simple-navigation'
8 | spec.version = SimpleNavigation::VERSION
9 | spec.authors = ['Andi Schacke', 'Mark J. Titorenko', 'Simon Courtois']
10 | spec.email = ['andi@codeplant.ch']
11 | spec.description = "With the simple-navigation gem installed you can easily " \
12 | "create multilevel navigations for your Rails, Sinatra or "\
13 | "Padrino applications. The navigation is defined in a " \
14 | "single configuration file. It supports automatic as well "\
15 | "as explicit highlighting of the currently active " \
16 | "navigation through regular expressions."
17 | spec.summary = "simple-navigation is a ruby library for creating navigations "\
18 | "(with multiple levels) for your Rails, Sinatra or " \
19 | "Padrino application."
20 | spec.homepage = 'http://github.com/codeplant/simple-navigation'
21 | spec.license = 'MIT'
22 |
23 | spec.files = `git ls-files -z`.split("\x0")
24 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
25 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
26 | spec.require_paths = ['lib']
27 |
28 | spec.rdoc_options = ['--inline-source', '--charset=UTF-8']
29 |
30 | spec.add_runtime_dependency 'activesupport', '>= 2.3.2'
31 |
32 | spec.add_development_dependency 'bundler'
33 | spec.add_development_dependency 'capybara'
34 | spec.add_development_dependency 'coveralls', '~> 0.7'
35 | spec.add_development_dependency 'guard-rspec', '~> 4.2'
36 | spec.add_development_dependency 'memfs', '~> 0.4.1'
37 | spec.add_development_dependency 'rake'
38 | spec.add_development_dependency 'rdoc'
39 | spec.add_development_dependency 'rspec', '~> 3.0'
40 | spec.add_development_dependency 'tzinfo', '>= 0'
41 | end
42 |
--------------------------------------------------------------------------------
/spec/fake_app/config/navigation.rb:
--------------------------------------------------------------------------------
1 | SimpleNavigation::Configuration.run do |navigation|
2 | navigation.items do |nav|
3 | nav.item :item_1, 'Item 1', '/item_1', html: {class: 'item_1'}, link_html: {id: 'link_1'}
4 | nav.item :item_2, 'Item 2', '/item_2', html: {class: 'item_2'}, link_html: {id: 'link_2'}
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/fake_app/rails_app.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 |
3 | require 'action_controller/railtie'
4 | require 'simple_navigation'
5 |
6 | module RailsApp
7 | class Application < Rails::Application
8 | config.active_support.deprecation = :log
9 | config.cache_classes = true
10 | config.eager_load = false
11 | config.root = __dir__
12 | config.secret_token = 'x'*100
13 | config.session_store :cookie_store, key: '_myapp_session'
14 | end
15 |
16 | class TestsController < ActionController::Base
17 | def base
18 | render inline: <<-END
19 |
20 |
21 |
22 | <%= render_navigation %>
23 |
24 |
25 | END
26 | end
27 | end
28 | end
29 |
30 | Rails.backtrace_cleaner.remove_silencers!
31 | RailsApp::Application.initialize!
32 |
33 | RailsApp::Application.routes.draw do
34 | get '/base_spec' => 'rails_app/tests#base'
35 | end
36 |
--------------------------------------------------------------------------------
/spec/initializers/coveralls.rb:
--------------------------------------------------------------------------------
1 | require 'coveralls'
2 |
3 | Coveralls.wear!
4 |
--------------------------------------------------------------------------------
/spec/initializers/have_css_matcher.rb:
--------------------------------------------------------------------------------
1 | RSpec::Matchers.define :have_css do |expected, times|
2 | match do |actual|
3 | selector = Nokogiri::HTML(actual).css(expected)
4 |
5 | if times
6 | expect(selector.size).to eq times
7 | else
8 | expect(selector.size).to be >= 1
9 | end
10 | end
11 |
12 | failure_message do |actual|
13 | "expected #{actual.to_s} to have #{times || 1} elements matching '#{expected}'"
14 | end
15 |
16 | failure_message_when_negated do |actual|
17 | "expected #{actual.to_s} not to have #{times || 1} elements matching '#{expected}'"
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/spec/initializers/memfs.rb:
--------------------------------------------------------------------------------
1 | require 'memfs'
2 |
3 | RSpec.configure do |config|
4 | config.around(memfs: true) do |example|
5 | MemFs.activate { example.run }
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/initializers/rails.rb:
--------------------------------------------------------------------------------
1 | begin
2 | require 'rails'
3 | rescue LoadError
4 | end
5 |
--------------------------------------------------------------------------------
/spec/initializers/rspec.rb:
--------------------------------------------------------------------------------
1 | RSpec.configure do |config|
2 | config.expect_with(:rspec) do |c|
3 | c.syntax = :expect
4 | end
5 |
6 | config.order = :random
7 | end
8 |
--------------------------------------------------------------------------------
/spec/integration/rendering_navigation_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.feature 'Rendering navigation' do
2 | background do
3 | SimpleNavigation.set_env(RailsApp::Application.root, 'test')
4 | end
5 |
6 | scenario 'Rendering basic navigation', type: :feature do
7 | visit '/base_spec'
8 |
9 | expect(page).to have_content('Item 1')
10 | expect(page).to have_content('Item 2')
11 | expect(page).to have_selector('li.item_1 a#link_1')
12 | expect(page).to have_selector('li.item_2 a#link_2')
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/simple_navigation/adapters/padrino_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | module Adapters
3 | describe Padrino do
4 | let(:adapter) { SimpleNavigation::Adapters::Padrino.new(context) }
5 | let(:content) { double(:content) }
6 | let(:context) { double(:context, request: request) }
7 | let(:request) { double(:request) }
8 |
9 | describe '#link_to' do
10 | it 'delegates to context' do
11 | expect(context).to receive(:link_to)
12 | .with('name', 'url', :my_option => true)
13 | adapter.link_to('name', 'url', :my_option => true)
14 | end
15 | end
16 |
17 | describe '#content_tag' do
18 | it 'delegates to context' do
19 | expect(content).to receive(:html_safe).and_return('content')
20 | expect(context).to receive(:content_tag)
21 | .with('type', 'content', my_option: true)
22 | adapter.content_tag('type', content, my_option: true)
23 | end
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/simple_navigation/adapters/rails_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | module Adapters
3 | describe Rails do
4 | let(:action_controller) { ActionController::Base }
5 | let(:adapter) { Rails.new(context) }
6 | let(:context) { double(:context, controller: controller) }
7 | let(:controller) { double(:controller) }
8 | let(:request) { double(:request) }
9 | let(:simple_navigation) { SimpleNavigation }
10 | let(:template) { double(:template, request: request) }
11 |
12 | describe '.register' do
13 | before { allow(action_controller).to receive(:include) }
14 |
15 | it 'calls set_env' do
16 | app_path = RailsApp::Application.root
17 | expect(simple_navigation).to receive(:set_env).with(app_path, 'test')
18 | simple_navigation.register
19 | end
20 |
21 | it 'extends the ActionController::Base with the Helpers' do
22 | expect(action_controller).to receive(:include)
23 | .with(SimpleNavigation::Helpers)
24 | simple_navigation.register
25 | end
26 |
27 | shared_examples 'installing helper method' do |method|
28 | it "installs the #{method} method as helper method" do
29 | simple_navigation.register
30 |
31 | helper_methods = action_controller.send(:_helper_methods)
32 | expect(helper_methods).to include(method)
33 | end
34 | end
35 |
36 | it_behaves_like 'installing helper method', :render_navigation
37 | it_behaves_like 'installing helper method', :active_navigation_item_name
38 | it_behaves_like 'installing helper method', :active_navigation_item_key
39 | it_behaves_like 'installing helper method', :active_navigation_item
40 | it_behaves_like 'installing helper method', :active_navigation_item_container
41 | end
42 |
43 | describe '#initialize' do
44 | context "when the controller's template is set" do
45 | before { allow(controller).to receive_messages(instance_variable_get: template) }
46 |
47 | it "sets the adapter's request accordingly" do
48 | expect(adapter.request).to be request
49 | end
50 | end
51 |
52 | context "when the controller's template is not set" do
53 | before { allow(controller).to receive_messages(instance_variable_get: nil) }
54 |
55 | it "sets the adapter's request to nil" do
56 | expect(adapter.request).to be_nil
57 | end
58 | end
59 |
60 | it "sets the adapter's controller to the context's controller" do
61 | expect(adapter.controller).to be controller
62 | end
63 |
64 | context "when the controller's template is stored as instance var (Rails2)" do
65 | context "when the controller's template is set" do
66 | before { allow(controller).to receive_messages(instance_variable_get: template) }
67 |
68 | it "sets the adapter's template accordingly" do
69 | expect(adapter.template).to be template
70 | end
71 | end
72 |
73 | context "when the controller's template is not set" do
74 | before { allow(controller).to receive_messages(instance_variable_get: nil) }
75 |
76 | it "set the adapter's template to nil" do
77 | expect(adapter.template).to be_nil
78 | end
79 | end
80 | end
81 |
82 | context "when the controller's template is stored as view_context (Rails3)" do
83 | context 'and the template is set' do
84 | before { allow(controller).to receive_messages(view_context: template) }
85 |
86 | it "sets the adapter's template accordingly" do
87 | expect(adapter.template).to be template
88 | end
89 | end
90 |
91 | context 'and the template is not set' do
92 | before { allow(controller).to receive_messages(view_context: nil) }
93 |
94 | it "sets the adapter's template to nil" do
95 | expect(adapter.template).to be_nil
96 | end
97 | end
98 | end
99 | end
100 |
101 | describe '#request_uri' do
102 | context "when the adapter's request is set" do
103 | before { allow(adapter).to receive_messages(request: request) }
104 |
105 | context 'and request.fullpath is defined' do
106 | let(:request) { double(:request, fullpath: '/fullpath') }
107 |
108 | it "sets the adapter's request_uri to the request.fullpath" do
109 | expect(adapter.request_uri).to eq '/fullpath'
110 | end
111 | end
112 |
113 | context 'and request.fullpath is not defined' do
114 | let(:request) { double(:request, request_uri: '/request_uri') }
115 |
116 | before { allow(adapter).to receive_messages(request: request) }
117 |
118 | it "sets the adapter's request_uri to the request.request_uri" do
119 | expect(adapter.request_uri).to eq '/request_uri'
120 | end
121 | end
122 | end
123 |
124 | context "when the adapter's request is not set" do
125 | before { allow(adapter).to receive_messages(request: nil) }
126 |
127 | it "sets the adapter's request_uri to an empty string" do
128 | expect(adapter.request_uri).to eq ''
129 | end
130 | end
131 | end
132 |
133 | describe '#request_path' do
134 | context "when the adapter's request is set" do
135 | let(:request) { double(:request, path: '/request_path') }
136 |
137 | before { allow(adapter).to receive_messages(request: request) }
138 |
139 | it "sets the adapter's request_path to the request.path" do
140 | expect(adapter.request_path).to eq '/request_path'
141 | end
142 | end
143 |
144 | context "when the adapter's request is not set" do
145 | before { allow(adapter).to receive_messages(request: nil) }
146 |
147 | it "sets the adapter's request_path to an empty string" do
148 | expect(adapter.request_path).to eq ''
149 | end
150 | end
151 | end
152 |
153 | describe '#context_for_eval' do
154 | context "when the adapter's controller is set" do
155 | before { adapter.instance_variable_set(:@controller, controller) }
156 |
157 | context "and the adapter's template is set" do
158 | before { adapter.instance_variable_set(:@template, template) }
159 |
160 | it "sets the adapter's context_for_eval to the template" do
161 | expect(adapter.context_for_eval).to be template
162 | end
163 | end
164 |
165 | context "and the adapter's template is not set" do
166 | before { adapter.instance_variable_set(:@template, nil) }
167 |
168 | it "sets the adapter's context_for_eval to the controller" do
169 | expect(adapter.context_for_eval).to be controller
170 | end
171 | end
172 | end
173 |
174 | context "when the adapter's controller is not set" do
175 | before { adapter.instance_variable_set(:@controller, nil) }
176 |
177 | context "and the adapter's template is set" do
178 | before { adapter.instance_variable_set(:@template, template) }
179 |
180 | it "sets the adapter's context_for_eval to the template" do
181 | expect(adapter.context_for_eval).to be template
182 | end
183 | end
184 |
185 | context "and the adapter's template is not set" do
186 | before { adapter.instance_variable_set(:@template, nil) }
187 |
188 | it 'raises an exception' do
189 | expect{ adapter.context_for_eval }.to raise_error(RuntimeError, 'no context set for evaluation the config file')
190 | end
191 | end
192 | end
193 | end
194 |
195 | describe '#current_page?' do
196 | context "when the adapter's template is set" do
197 | before { allow(adapter).to receive_messages(template: template) }
198 |
199 | it 'delegates the call to the template' do
200 | expect(template).to receive(:current_page?).with(:page)
201 | adapter.current_page?(:page)
202 | end
203 | end
204 |
205 | context "when the adapter's template is not set" do
206 | before { allow(adapter).to receive_messages(template: nil) }
207 |
208 | it 'returns false' do
209 | expect(adapter.current_page?(:page)).to be_falsey
210 | end
211 | end
212 |
213 | context 'when the given url is nil' do
214 | it 'returns false' do
215 | expect(adapter.current_page?(nil)).to be_falsey
216 | end
217 | end
218 | end
219 |
220 | describe '#link_to' do
221 | let(:options) { double(:options) }
222 |
223 | context "when the adapter's template is set" do
224 | before { allow(adapter).to receive_messages(template: template, html_safe: 'safe_text') }
225 |
226 | context 'with considering item names as safe' do
227 | before { SimpleNavigation.config.consider_item_names_as_safe = true }
228 | after { SimpleNavigation.config.consider_item_names_as_safe = false }
229 |
230 | it 'delegates the call to the template (with html_safe text)' do
231 | expect(template).to receive(:link_to)
232 | .with('safe_text', 'url', options)
233 | adapter.link_to('text', 'url', options)
234 | end
235 | end
236 |
237 | context 'with considering item names as UNsafe (default)' do
238 |
239 | it 'delegates the call to the template (with html_safe text)' do
240 | expect(template).to receive(:link_to)
241 | .with('text', 'url', options)
242 | adapter.link_to('text', 'url', options)
243 | end
244 | end
245 |
246 | end
247 |
248 | context "when the adapter's template is not set" do
249 | before { allow(adapter).to receive_messages(template: nil) }
250 |
251 | it 'returns nil' do
252 | expect(adapter.link_to('text', 'url', options)).to be_nil
253 | end
254 | end
255 | end
256 |
257 | describe '#content_tag' do
258 | let(:options) { double(:options) }
259 |
260 | context "when the adapter's template is set" do
261 | before { allow(adapter).to receive_messages(template: template, html_safe: 'safe_text') }
262 |
263 | it 'delegates the call to the template (with html_safe text)' do
264 | expect(template).to receive(:content_tag)
265 | .with(:div, 'safe_text', options)
266 | adapter.content_tag(:div, 'text', options)
267 | end
268 | end
269 |
270 | context "when the adapter's template is not set" do
271 | before { allow(adapter).to receive_messages(template: nil) }
272 |
273 | it 'returns nil' do
274 | expect(adapter.content_tag(:div, 'text', options)).to be_nil
275 | end
276 | end
277 | end
278 |
279 | end
280 | end
281 | end
282 |
--------------------------------------------------------------------------------
/spec/simple_navigation/adapters/sinatra_spec.rb:
--------------------------------------------------------------------------------
1 | describe SimpleNavigation::Adapters::Sinatra do
2 | let(:adapter) { SimpleNavigation::Adapters::Sinatra.new(context) }
3 | let(:context) { double(:context) }
4 | let(:request) { double(:request, fullpath: '/full?param=true', path: '/full') }
5 |
6 | before { allow(context).to receive_messages(request: request) }
7 |
8 | describe '#context_for_eval' do
9 | context "when adapter's context is not set" do
10 | it 'raises an exception' do
11 | allow(adapter).to receive_messages(context: nil)
12 | expect{ adapter.context_for_eval }.to raise_error(RuntimeError, 'no context set for evaluation the config file')
13 | end
14 | end
15 |
16 | context "when adapter's context is set" do
17 | it 'returns the context' do
18 | expect(adapter.context_for_eval).to be context
19 | end
20 | end
21 | end
22 |
23 | describe '#request_uri' do
24 | it 'returns the request.fullpath' do
25 | expect(adapter.request_uri).to eq '/full?param=true'
26 | end
27 | end
28 |
29 | describe '#request_path' do
30 | it 'returns the request.path' do
31 | expect(adapter.request_path).to eq '/full'
32 | end
33 | end
34 |
35 | describe '#current_page?' do
36 | before { allow(request).to receive_messages(scheme: 'http', host_with_port: 'my_host:5000') }
37 |
38 | shared_examples 'detecting current page' do |url, expected|
39 | context "when url is #{url}" do
40 | it "returns #{expected}" do
41 | expect(adapter.current_page?(url)).to be expected
42 | end
43 | end
44 | end
45 |
46 | context 'when URL is not encoded' do
47 | it_behaves_like 'detecting current page', '/full?param=true', true
48 | it_behaves_like 'detecting current page', '/full?param3=true', false
49 | it_behaves_like 'detecting current page', '/full', true
50 | it_behaves_like 'detecting current page', 'http://my_host:5000/full?param=true', true
51 | it_behaves_like 'detecting current page', 'http://my_host:5000/full?param3=true', false
52 | it_behaves_like 'detecting current page', 'http://my_host:5000/full', true
53 | it_behaves_like 'detecting current page', 'https://my_host:5000/full', false
54 | it_behaves_like 'detecting current page', 'http://my_host:6000/full', false
55 | it_behaves_like 'detecting current page', 'http://my_other_host:5000/full', false
56 | end
57 |
58 | context 'when URL is encoded' do
59 | before do
60 | allow(request).to receive_messages(fullpath: '/full%20with%20spaces?param=true',
61 | path: '/full%20with%20spaces')
62 | end
63 |
64 | it_behaves_like 'detecting current page', '/full%20with%20spaces?param=true', true
65 | it_behaves_like 'detecting current page', '/full%20with%20spaces?param3=true', false
66 | it_behaves_like 'detecting current page', '/full%20with%20spaces', true
67 | it_behaves_like 'detecting current page', 'http://my_host:5000/full%20with%20spaces?param=true', true
68 | it_behaves_like 'detecting current page', 'http://my_host:5000/full%20with%20spaces?param3=true', false
69 | it_behaves_like 'detecting current page', 'http://my_host:5000/full%20with%20spaces', true
70 | it_behaves_like 'detecting current page', 'https://my_host:5000/full%20with%20spaces', false
71 | it_behaves_like 'detecting current page', 'http://my_host:6000/full%20with%20spaces', false
72 | it_behaves_like 'detecting current page', 'http://my_other_host:5000/full%20with%20spaces', false
73 | end
74 | end
75 |
76 | describe '#link_to' do
77 | it 'returns a link with the correct class and id' do
78 | link = adapter.link_to('link', 'url', class: 'clazz', id: 'id')
79 | expect(link).to eq "link"
80 | end
81 | end
82 |
83 | describe '#content_tag' do
84 | it 'returns a tag with the correct class and id' do
85 | tag = adapter.content_tag(:div, 'content', class: 'clazz', id: 'id')
86 | expect(tag).to eq "
content
"
87 | end
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/spec/simple_navigation/config_file_finder_spec.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 | require 'simple_navigation/config_file_finder'
3 |
4 | module SimpleNavigation
5 | describe ConfigFileFinder do
6 | subject(:finder) { ConfigFileFinder.new(paths) }
7 |
8 | let(:paths) { ['/path/one', '/path/two'] }
9 |
10 | describe '#find', memfs: true do
11 | before { FileUtils.mkdir_p(paths) }
12 |
13 | context 'when the context is :default' do
14 | let(:context) { :default }
15 |
16 | context 'and a navigation.rb file is found in one of the paths' do
17 | before { FileUtils.touch('/path/one/navigation.rb') }
18 |
19 | it 'returns its full path' do
20 | expect(finder.find(context)).to eq '/path/one/navigation.rb'
21 | end
22 | end
23 |
24 | context 'and no navigation.rb file is found in the paths' do
25 | it 'raises an exception' do
26 | expect { finder.find(context) }.to raise_error(RuntimeError, /Config file 'navigation.rb' not found in path\(s\)/)
27 | end
28 | end
29 | end
30 |
31 | context 'when the context is :other' do
32 | let(:context) { :other }
33 |
34 | context 'and a other_navigation.rb file is found in one of the paths' do
35 | before { FileUtils.touch('/path/two/other_navigation.rb') }
36 |
37 | it 'returns its full path' do
38 | expect(finder.find(context)).to eq '/path/two/other_navigation.rb'
39 | end
40 | end
41 |
42 | context 'and no other_navigation.rb file is found in the paths' do
43 | it 'raise an exception' do
44 | expect{ finder.find(context) }.to raise_error(RuntimeError, /Config file 'other_navigation.rb' not found in path\(s\)/)
45 | end
46 | end
47 | end
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/spec/simple_navigation/config_file_spec.rb:
--------------------------------------------------------------------------------
1 | require 'simple_navigation/config_file'
2 |
3 | module SimpleNavigation
4 | describe ConfigFile do
5 | subject(:config_file) { ConfigFile.new(context) }
6 |
7 | let(:context) { :default }
8 |
9 | describe '#name' do
10 | context 'when the context is :default' do
11 | it 'returns navigation.rb' do
12 | expect(config_file.name).to eq 'navigation.rb'
13 | end
14 | end
15 |
16 | context 'when the context is different from :default' do
17 | let(:context) { :HelloWorld }
18 |
19 | it 'returns UNDERSCORED_CONTEXT_navigation.rb' do
20 | expect(config_file.name).to eq 'hello_world_navigation.rb'
21 | end
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/simple_navigation/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | describe Configuration do
3 | subject(:config) { Configuration.instance }
4 |
5 | describe '.run' do
6 | it "yields the singleton Configuration object" do
7 | expect{ |blk| Configuration.run(&blk) }.to yield_with_args(config)
8 | end
9 | end
10 |
11 | describe '.eval_config' do
12 | let(:config_files) {{ default: 'default', my_context: 'my_context' }}
13 | let(:eval_context) { double(:eval_context) }
14 |
15 | before do
16 | allow(eval_context).to receive(:instance_eval)
17 | allow(SimpleNavigation).to \
18 | receive_messages(context_for_eval: eval_context, config_files: config_files)
19 | end
20 |
21 | context "with default navigation context" do
22 | it "calls instance_eval with the default config_file-string inside the context" do
23 | expect(eval_context).to receive(:instance_eval).with('default')
24 | Configuration.eval_config
25 | end
26 | end
27 |
28 | context 'with non default navigation context' do
29 | it "calls instance_eval with the specified config_file-string inside the context" do
30 | expect(eval_context).to receive(:instance_eval).with('my_context')
31 | Configuration.eval_config(:my_context)
32 | end
33 | end
34 | end
35 |
36 | describe '#initialize' do
37 | it 'sets the List-Renderer as default' do
38 | expect(config.renderer).to be Renderer::List
39 | end
40 |
41 | it 'sets the selected_class to "selected" as default' do
42 | expect(config.selected_class).to eq 'selected'
43 | end
44 |
45 | it 'sets the active_leaf_class to "simple-navigation-active-leaf" as default' do
46 | expect(config.active_leaf_class).to eq 'simple-navigation-active-leaf'
47 | end
48 |
49 | it 'sets autogenerate_item_ids to true as default' do
50 | expect(config.autogenerate_item_ids).to be true
51 | end
52 |
53 | it 'sets auto_highlight to true as default' do
54 | expect(config.auto_highlight).to be true
55 | end
56 |
57 | it 'sets the id_generator to a callable object' do
58 | expect(config.id_generator).to respond_to(:call)
59 | end
60 |
61 | it 'sets the name_generator to a callable object' do
62 | expect(config.name_generator).to respond_to(:call)
63 | end
64 |
65 | it 'sets the consider_item_names_as_safe to false' do
66 | expect(config.consider_item_names_as_safe).to be false
67 | end
68 |
69 | it 'sets highlights_on_subpath to false as default' do
70 | expect(config.highlight_on_subpath).to be false
71 | end
72 |
73 | it 'sets ignore_query_params_on_auto_highlight to true as default' do
74 | expect(config.ignore_query_params_on_auto_highlight).to be true
75 | end
76 |
77 | it 'sets ignore_anchors_on_auto_highlight to true as default' do
78 | expect(config.ignore_anchors_on_auto_highlight).to be true
79 | end
80 | end
81 |
82 | describe '#items' do
83 | let(:container) { double(:items_container) }
84 |
85 | before { allow(ItemContainer).to receive_messages(new: container) }
86 |
87 | context 'when a block is given' do
88 | context 'and items_provider is specified' do
89 | let(:provider) { double(:provider) }
90 |
91 | it 'raises an exception' do
92 | expect{ config.items(provider) {} }.to raise_error(RuntimeError, 'please specify either items_provider or block, but not both')
93 | end
94 | end
95 |
96 | context 'when no items_provider is specified' do
97 | it 'yields an new ItemContainer' do
98 | expect{ |blk| config.items(&blk) }.to yield_with_args(container)
99 | end
100 |
101 | it 'assigns the ItemContainer to an instance-var' do
102 | config.items {}
103 | expect(config.primary_navigation).to be container
104 | end
105 |
106 | it "doesn't set the items on the container" do
107 | expect(container).not_to receive(:items=)
108 | config.items {}
109 | end
110 | end
111 | end
112 |
113 | context 'when no block is given' do
114 | context 'and items_provider is specified' do
115 | let(:external_provider) { double(:external_provider) }
116 | let(:items) { double(:items) }
117 | let(:items_provider) { double(:items_provider, items: items) }
118 |
119 | before do
120 | allow(SimpleNavigation::ItemsProvider).to receive_messages(new: items_provider)
121 | allow(container).to receive(:items=)
122 | end
123 |
124 | it 'creates a new Provider object for the specified provider' do
125 | expect(ItemsProvider).to receive(:new).with(external_provider)
126 | config.items(external_provider)
127 | end
128 |
129 | it 'calls items on the provider object' do
130 | expect(items_provider).to receive(:items)
131 | config.items(external_provider)
132 | end
133 |
134 | it 'sets the items on the container' do
135 | expect(container).to receive(:items=).with(items)
136 | config.items(external_provider)
137 | end
138 | end
139 |
140 | context 'when items_provider is not specified' do
141 | it "raises an exception" do
142 | expect{ config.items }.to raise_error(RuntimeError, 'please specify either items_provider or block, but not both')
143 | end
144 | end
145 | end
146 | end
147 |
148 | describe '#loaded?' do
149 | context 'when primary_nav is set' do
150 | it 'returns true' do
151 | config.instance_variable_set(:@primary_navigation, :bla)
152 | expect(config).to be_loaded
153 | end
154 | end
155 |
156 | context 'when primary_nav is not set' do
157 | it 'returns false if no primary_nav is set' do
158 | config.instance_variable_set(:@primary_navigation, nil)
159 | expect(config).not_to be_loaded
160 | end
161 | end
162 | end
163 | end
164 | end
165 |
--------------------------------------------------------------------------------
/spec/simple_navigation/helpers_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | describe Helpers do
3 | subject(:controller) { test_controller_class.new }
4 |
5 | let(:invoices_item) { navigation[:invoices] }
6 | let(:item) { nil }
7 | let(:navigation) { setup_navigation('nav_id', 'nav_class') }
8 | let(:test_controller_class) do
9 | Class.new { include SimpleNavigation::Helpers }
10 | end
11 | let(:unpaid_item) { invoices_item.sub_navigation[:unpaid] }
12 |
13 | before do
14 | allow(Configuration).to receive(:eval_config)
15 | allow(SimpleNavigation).to receive_messages(load_config: nil,
16 | primary_navigation: navigation,
17 | config_file?: true,
18 | context_for_eval: controller)
19 |
20 | select_an_item(navigation[item]) if item
21 | end
22 |
23 | describe '#active_navigation_item_name' do
24 | context 'when no item is selected' do
25 | it 'returns an empty string for no parameters' do
26 | expect(controller.active_navigation_item_name).to eq ''
27 | end
28 |
29 | it "returns an empty string for level: 1" do
30 | item_name = controller.active_navigation_item_name(level: 1)
31 | expect(item_name).to eq ''
32 | end
33 |
34 | it 'returns an empty string for level: 2' do
35 | item_name = controller.active_navigation_item_name(level: 2)
36 | expect(item_name).to eq ''
37 | end
38 |
39 | it 'returns an empty string for level: :all' do
40 | item_name = controller.active_navigation_item_name(level: :all)
41 | expect(item_name).to eq ''
42 | end
43 | end
44 |
45 | context 'when an item is selected' do
46 | context "and it's a primary item" do
47 | let(:item) { :invoices }
48 |
49 | it 'returns an empty string' do
50 | expect(controller.active_navigation_item_name).to eq ''
51 | end
52 |
53 | it "returns the selected item's name for level: 1" do
54 | item_name = controller.active_navigation_item_name(level: 1)
55 | expect(item_name).to eq 'Invoices'
56 | end
57 |
58 | it 'returns an empty string for level: 2' do
59 | item_name = controller.active_navigation_item_name(level: 2)
60 | expect(item_name).to eq ''
61 | end
62 |
63 | it 'returns an empty string for level: :all' do
64 | item_name = controller.active_navigation_item_name(level: :all)
65 | expect(item_name).to eq ''
66 | end
67 | end
68 |
69 | context "and it's a sub navigation item" do
70 | before do
71 | select_an_item(invoices_item)
72 | select_an_item(unpaid_item)
73 | end
74 |
75 | it "returns the selected item's name" do
76 | expect(controller.active_navigation_item_name).to eq 'Unpaid'
77 | end
78 |
79 | it "returns the selected item's parent name for level: 1" do
80 | item_name = controller.active_navigation_item_name(level: 1)
81 | expect(item_name).to eq 'Invoices'
82 | end
83 |
84 | it "returns the selected item's name for level: 2" do
85 | item_name = controller.active_navigation_item_name(level: 2)
86 | expect(item_name).to eq 'Unpaid'
87 | end
88 |
89 | it "returns the selected item's name for level: :all" do
90 | item_name = controller.active_navigation_item_name(level: :all)
91 | expect(item_name).to eq 'Unpaid'
92 | end
93 | end
94 | end
95 | end
96 |
97 | describe '#active_navigation_item_key' do
98 | context 'when no item is selected' do
99 | it 'returns nil' do
100 | expect(controller.active_navigation_item_key).to be_nil
101 | end
102 |
103 | it 'returns nil for no parameters' do
104 | expect(controller.active_navigation_item_key).to be_nil
105 | end
106 |
107 | it "returns nil for level: 1" do
108 | item_key = controller.active_navigation_item_key(level: 1)
109 | expect(item_key).to be_nil
110 | end
111 |
112 | it 'returns nil for level: 2' do
113 | item_key = controller.active_navigation_item_key(level: 2)
114 | expect(item_key).to be_nil
115 | end
116 |
117 | it 'returns nil for level: :all' do
118 | item_key = controller.active_navigation_item_key(level: :all)
119 | expect(item_key).to be_nil
120 | end
121 | end
122 |
123 | context 'when an item is selected' do
124 | context "and it's a primary item" do
125 | let(:item) { :invoices }
126 |
127 | it 'returns nil for no parameters' do
128 | expect(controller.active_navigation_item_key).to be_nil
129 | end
130 |
131 | it "returns the selected item's name for level: 1" do
132 | item_key = controller.active_navigation_item_key(level: 1)
133 | expect(item_key).to eq :invoices
134 | end
135 |
136 | it 'returns nil for level: 2' do
137 | item_key = controller.active_navigation_item_key(level: 2)
138 | expect(item_key).to be_nil
139 | end
140 |
141 | it 'returns nil for level: :all' do
142 | item_key = controller.active_navigation_item_key(level: :all)
143 | expect(item_key).to be_nil
144 | end
145 | end
146 |
147 | context "and it's a sub navigation item" do
148 | before do
149 | select_an_item(invoices_item)
150 | select_an_item(unpaid_item)
151 | end
152 |
153 | it "returns the selected item's name" do
154 | expect(controller.active_navigation_item_key).to eq :unpaid
155 | end
156 |
157 | it "returns the selected item's parent name for level: 1" do
158 | item_key = controller.active_navigation_item_key(level: 1)
159 | expect(item_key).to eq :invoices
160 | end
161 |
162 | it "returns the selected item's name for level: 2" do
163 | item_key = controller.active_navigation_item_key(level: 2)
164 | expect(item_key).to eq :unpaid
165 | end
166 |
167 | it "returns the selected item's name for level: :all" do
168 | item_key = controller.active_navigation_item_key(level: :all)
169 | expect(item_key).to eq :unpaid
170 | end
171 | end
172 | end
173 | end
174 |
175 | describe '#active_navigation_item' do
176 | context 'when no item is selected' do
177 | it 'returns nil for no parameters' do
178 | expect(controller.active_navigation_item).to be_nil
179 | end
180 |
181 | it "returns nil for level: 1" do
182 | item_key = controller.active_navigation_item(level: 1)
183 | expect(item_key).to be_nil
184 | end
185 |
186 | it 'returns nil for level: 2' do
187 | item_key = controller.active_navigation_item(level: 2)
188 | expect(item_key).to be_nil
189 | end
190 |
191 | it 'returns nil for level: :all' do
192 | item_key = controller.active_navigation_item(level: :all)
193 | expect(item_key).to be_nil
194 | end
195 | end
196 |
197 | context 'when an item is selected' do
198 | context "and it's a primary item" do
199 | let(:item) { :invoices }
200 |
201 | it 'returns nil for no parameters' do
202 | expect(controller.active_navigation_item).to be_nil
203 | end
204 |
205 | it "returns the selected item's name for level: 1" do
206 | item_key = controller.active_navigation_item(level: 1)
207 | expect(item_key).to be invoices_item
208 | end
209 |
210 | it 'returns nil for level: 2' do
211 | item_key = controller.active_navigation_item(level: 2)
212 | expect(item_key).to be_nil
213 | end
214 |
215 | it 'returns nil for level: :all' do
216 | item_key = controller.active_navigation_item(level: :all)
217 | expect(item_key).to be_nil
218 | end
219 | end
220 |
221 | context "and it's a sub navigation item" do
222 | before do
223 | select_an_item(invoices_item)
224 | select_an_item(unpaid_item)
225 | end
226 |
227 | it "returns the selected item's name for no parameters" do
228 | expect(controller.active_navigation_item).to be unpaid_item
229 | end
230 |
231 | it "returns the selected item's parent name for level: 1" do
232 | item_key = controller.active_navigation_item(level: 1)
233 | expect(item_key).to be invoices_item
234 | end
235 |
236 | it "returns the selected item's name for level: 2" do
237 | item_key = controller.active_navigation_item(level: 2)
238 | expect(item_key).to eq unpaid_item
239 | end
240 |
241 | it "returns the selected item's name for level: :all" do
242 | item_key = controller.active_navigation_item(level: :all)
243 | expect(item_key).to eq unpaid_item
244 | end
245 | end
246 | end
247 | end
248 |
249 | describe '#active_navigation_item_container' do
250 | shared_examples 'returning items container' do
251 | it 'returns the primary navigation for no parameters' do
252 | expect(controller.active_navigation_item_container).to be navigation
253 | end
254 |
255 | it "returns the primary navigation for level: 1" do
256 | item_container = controller.active_navigation_item_container(level: 1)
257 | expect(item_container).to be navigation
258 | end
259 |
260 | it 'returns the primary navigation level: :all' do
261 | item_container =
262 | controller.active_navigation_item_container(level: :all)
263 | expect(item_container).to be navigation
264 | end
265 | end
266 |
267 | context 'when no item is selected' do
268 | it_behaves_like 'returning items container'
269 |
270 | it 'returns nil for level: 2' do
271 | item_container = controller.active_navigation_item_container(level: 2)
272 | expect(item_container).to be_nil
273 | end
274 | end
275 |
276 | context 'when an item is selected' do
277 | context "and it's a primary item" do
278 | let(:item) { :invoices }
279 |
280 | it_behaves_like 'returning items container'
281 |
282 | it 'returns the invoices items container for level: 2' do
283 | item_container =
284 | controller.active_navigation_item_container(level: 2)
285 | expect(item_container).to be invoices_item.sub_navigation
286 | end
287 | end
288 |
289 | context "and it's a sub navigation item" do
290 | before do
291 | select_an_item(invoices_item)
292 | select_an_item(unpaid_item)
293 | end
294 |
295 | it_behaves_like 'returning items container'
296 |
297 | it 'returns the invoices items container for level: 2' do
298 | item_container =
299 | controller.active_navigation_item_container(level: 2)
300 | expect(item_container).to be invoices_item.sub_navigation
301 | end
302 | end
303 | end
304 | end
305 |
306 | describe '#render_navigation' do
307 | it 'evaluates the configuration on every request' do
308 | expect(SimpleNavigation).to receive(:load_config).twice
309 | 2.times { controller.render_navigation }
310 | end
311 |
312 | it 'loads the :default configuration' do
313 | expect(SimpleNavigation).to receive(:load_config).with(:default)
314 | controller.render_navigation
315 | end
316 |
317 | it "doesn't set the items directly" do
318 | expect(SimpleNavigation.config).not_to receive(:items)
319 | controller.render_navigation
320 | end
321 |
322 | it 'looks up the active_item_container based on the level' do
323 | expect(SimpleNavigation).to receive(:active_item_container_for)
324 | .with(:all)
325 | controller.render_navigation
326 | end
327 |
328 | context 'when the :context option is specified' do
329 | it 'loads the configuration for the specified context' do
330 | expect(SimpleNavigation).to receive(:load_config).with(:my_context)
331 | controller.render_navigation(context: :my_context)
332 | end
333 | end
334 |
335 | context 'when the :items option is specified' do
336 | let(:items) { double(:items) }
337 |
338 | it 'sets the items directly' do
339 | expect(SimpleNavigation.config).to receive(:items).with(items)
340 | controller.render_navigation(items: items)
341 | end
342 | end
343 |
344 | context 'when the :level option is set' do
345 | context 'and its value is 1' do
346 | it 'calls render on the primary navigation' do
347 | expect(navigation).to receive(:render).with(level: 1)
348 | controller.render_navigation(level: 1)
349 | end
350 | end
351 |
352 | context 'and its value is 2' do
353 | context 'and the active_item_container is set' do
354 | let(:item_container) { double(:container).as_null_object }
355 |
356 | before do
357 | allow(SimpleNavigation).to receive_messages(active_item_container_for: item_container)
358 | end
359 |
360 | it 'finds the selected sub navigation for the specified level' do
361 | expect(SimpleNavigation).to receive(:active_item_container_for)
362 | .with(2)
363 | controller.render_navigation(level: 2)
364 | end
365 |
366 | it 'calls render on the active item_container' do
367 | expect(item_container).to receive(:render).with(level: 2)
368 | controller.render_navigation(level: 2)
369 | end
370 | end
371 |
372 | context "and the active_item_container isn't set" do
373 | it "doesn't raise an exception" do
374 | expect{
375 | controller.render_navigation(level: 2)
376 | }.not_to raise_error
377 | end
378 | end
379 | end
380 |
381 | context "and its value isn't a valid level" do
382 | it 'raises an exception' do
383 | expect{
384 | controller.render_navigation(level: :invalid)
385 | }.to raise_error(ArgumentError, 'Invalid navigation level: invalid')
386 | end
387 | end
388 | end
389 |
390 | context 'when the :levels option is set' do
391 | before { allow(SimpleNavigation).to receive_messages(active_item_container_for: navigation) }
392 |
393 | it 'treats it like the :level option' do
394 | expect(navigation).to receive(:render).with(level: 2)
395 | controller.render_navigation(levels: 2)
396 | end
397 | end
398 |
399 | context 'when a block is given' do
400 | it 'calls the block passing it an item container' do
401 | expect{ |blk|
402 | controller.render_navigation(&blk)
403 | }.to yield_with_args(ItemContainer)
404 | end
405 | end
406 |
407 | context 'when no primary configuration is defined' do
408 | before { allow(SimpleNavigation).to receive_messages(primary_navigation: nil) }
409 |
410 | it 'raises an exception' do
411 | expect{controller.render_navigation}.to raise_error(RuntimeError, 'no primary navigation defined, either use a navigation config file or pass items directly to render_navigation')
412 | end
413 | end
414 |
415 | context "when active_item_container is set" do
416 | let(:active_item_container) { double(:container).as_null_object }
417 |
418 | before do
419 | allow(SimpleNavigation).to receive_messages(active_item_container_for: active_item_container)
420 | end
421 |
422 | it 'calls render on the active_item_container' do
423 | expect(active_item_container).to receive(:render)
424 | controller.render_navigation
425 | end
426 | end
427 | end
428 | end
429 | end
430 |
--------------------------------------------------------------------------------
/spec/simple_navigation/item_adapter_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | describe ItemAdapter do
3 | let(:item_adapter) { ItemAdapter.new(item) }
4 |
5 | context 'when item is an object' do
6 | let(:item) { double(:item, key: 'key', name: 'name', url: 'url') }
7 |
8 | shared_examples 'delegating to item' do |meth|
9 | it "delegates #{meth} to item" do
10 | expect(item).to receive(meth)
11 | item_adapter.public_send(meth)
12 | end
13 | end
14 |
15 | it_behaves_like 'delegating to item', :key
16 | it_behaves_like 'delegating to item', :url
17 | it_behaves_like 'delegating to item', :name
18 |
19 | describe '#initialize' do
20 | it 'sets the item' do
21 | expect(item_adapter.item).to be item
22 | end
23 | end
24 |
25 | describe '#options' do
26 | context 'when item responds to options' do
27 | let(:options) { double(:options) }
28 |
29 | before { allow(item).to receive_messages(options: options) }
30 |
31 | it "returns the item's options" do
32 | expect(item_adapter.options).to be options
33 | end
34 | end
35 |
36 | context 'item does not respond to options' do
37 | it 'returns an empty hash' do
38 | expect(item_adapter.options).to eq({})
39 | end
40 | end
41 | end
42 |
43 | describe '#items' do
44 | context 'when item responds to items' do
45 | context 'and items is nil' do
46 | before { allow(item).to receive_messages(items: nil) }
47 |
48 | it 'returns nil' do
49 | expect(item_adapter.items).to be_nil
50 | end
51 | end
52 |
53 | context 'when items is not nil' do
54 | context 'and items is empty' do
55 | before { allow(item).to receive_messages(items: []) }
56 |
57 | it 'returns nil' do
58 | expect(item_adapter.items).to be_nil
59 | end
60 | end
61 |
62 | context 'and items is not empty' do
63 | let(:items) { [true] }
64 |
65 | before { allow(item).to receive_messages(items: items) }
66 |
67 | it 'returns the items' do
68 | expect(item_adapter.items).to eq items
69 | end
70 | end
71 | end
72 | end
73 |
74 | context "when item doesn't respond to items" do
75 | it 'returns nil' do
76 | expect(item_adapter.items).to be_nil
77 | end
78 | end
79 | end
80 |
81 | describe '#to_simple_navigation_item' do
82 | let(:container) { double(:container) }
83 |
84 | before { allow(item).to receive_messages(items: [], options: {}) }
85 |
86 | it 'creates an Item' do
87 | expect(Item).to receive(:new)
88 | .with(container, 'key', 'name', 'url', {})
89 | item_adapter.to_simple_navigation_item(container)
90 | end
91 | end
92 | end
93 |
94 | context 'when item is a kind of hash' do
95 | class ModifiedHash < Hash; end
96 |
97 | let(:item) { ModifiedHash[key: 'key', url: 'url', name: 'name'] }
98 |
99 | shared_examples 'delegating to item' do |meth|
100 | it "delegates #{meth} to item" do
101 | expect(item_adapter.item).to receive(meth)
102 | item_adapter.public_send(meth)
103 | end
104 | end
105 |
106 | it_behaves_like 'delegating to item', :key
107 | it_behaves_like 'delegating to item', :url
108 | it_behaves_like 'delegating to item', :name
109 |
110 | describe '#initialize' do
111 | it 'sets the item' do
112 | expect(item_adapter.item).not_to be_nil
113 | end
114 |
115 | it 'converts the item into an object' do
116 | expect(item_adapter.item).to respond_to(:url)
117 | end
118 | end
119 |
120 | describe '#options' do
121 | context 'when item responds to options' do
122 | before { item[:options] = { my: :options } }
123 |
124 | it "returns the item's options" do
125 | expect(item_adapter.options).to eq({ my: :options })
126 | end
127 | end
128 |
129 | context 'when item does not respond to options' do
130 | it 'returns an empty hash' do
131 | expect(item_adapter.options).to eq({})
132 | end
133 | end
134 | end
135 |
136 | describe '#items' do
137 | context 'when item responds to items' do
138 | context 'and items is nil' do
139 | before { item[:items] = nil }
140 |
141 | it 'returns nil' do
142 | expect(item_adapter.items).to be_nil
143 | end
144 | end
145 |
146 | context 'when items is not nil' do
147 | context 'and items is empty' do
148 | it 'returns nil' do
149 | expect(item_adapter.items).to be_nil
150 | end
151 | end
152 |
153 | context 'and items is not empty' do
154 | before { item[:items] = ['not', 'empty'] }
155 |
156 | it 'returns the items' do
157 | expect(item_adapter.items).to eq ['not', 'empty']
158 | end
159 | end
160 | end
161 | end
162 |
163 | context 'when item does not respond to items' do
164 | it 'returns nil' do
165 | expect(item_adapter.items).to be_nil
166 | end
167 | end
168 | end
169 |
170 | describe '#to_simple_navigation_item' do
171 | let(:container) { double(:container) }
172 |
173 | before { item.merge(options: {}) }
174 |
175 | it 'passes the right arguments to Item' do
176 | expect(Item).to receive(:new)
177 | .with(container, 'key', 'name', 'url', {})
178 | item_adapter.to_simple_navigation_item(container)
179 | end
180 |
181 | it 'creates an Item' do
182 | created_item = item_adapter.to_simple_navigation_item(container)
183 | expect(created_item).to be_an(Item)
184 | end
185 | end
186 | end
187 | end
188 | end
189 |
--------------------------------------------------------------------------------
/spec/simple_navigation/item_container_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | describe ItemContainer do
3 | subject(:item_container) { ItemContainer.new }
4 |
5 | shared_examples 'adding the item to the list' do
6 | it 'adds the item to the list' do
7 | allow(Item).to receive_messages(new: item)
8 | item_container.item(*args)
9 | expect(item_container.items).to include(item)
10 | end
11 | end
12 |
13 | shared_examples 'not adding the item to the list' do
14 | it "doesn't add the item to the list" do
15 | allow(Item).to receive_messages(new: item)
16 | item_container.item(*args)
17 | expect(item_container.items).not_to include(item)
18 | end
19 | end
20 |
21 | describe '#initialize' do
22 | it 'sets an empty items array' do
23 | expect(item_container.items).to be_empty
24 | end
25 | end
26 |
27 | describe '#dom_attributes' do
28 | let(:dom_attributes) {{ id: 'test_id', class: 'test_class' }}
29 |
30 | before { item_container.dom_attributes = dom_attributes }
31 |
32 | it "returns the container's dom_attributes" do
33 | expect(item_container.dom_attributes).to eq dom_attributes
34 | end
35 |
36 | context 'when the dom_attributes do not contain any id or class' do
37 | let(:dom_attributes) {{ test: 'test' }}
38 |
39 | context "and the container hasn't any dom_id" do
40 | it "returns the contaier's dom_attributes without any id" do
41 | expect(item_container.dom_attributes).not_to include(:id)
42 | end
43 | end
44 |
45 | context 'and the container has a dom_id' do
46 | before { item_container.dom_id = 'test_id' }
47 |
48 | it "returns the contaier's dom_attributes including the #dom_id" do
49 | expect(item_container.dom_attributes).to include(id: 'test_id')
50 | end
51 | end
52 |
53 | context "and the container hasn't any dom_class" do
54 | it "returns the contaier's dom_attributes without any class" do
55 | expect(item_container.dom_attributes).not_to include(:class)
56 | end
57 | end
58 |
59 | context 'and the container has a dom_class' do
60 | before { item_container.dom_class = 'test_class' }
61 |
62 | it "returns the contaier's dom_attributes including the #dom_class" do
63 | expect(item_container.dom_attributes).to include(class: 'test_class')
64 | end
65 | end
66 | end
67 | end
68 |
69 | describe '#items=' do
70 | let(:item) {{ key: :my_key, name: 'test', url: '/' }}
71 | let(:items) { [item] }
72 | let(:item_adapter) { double(:item_adapter).as_null_object }
73 | let(:real_item) { double(:real_item) }
74 |
75 | before do
76 | allow(ItemAdapter).to receive_messages(new: item_adapter)
77 | allow(item_adapter).to receive(:to_simple_navigation_item)
78 | .with(item_container)
79 | .and_return(real_item)
80 | end
81 |
82 | context 'when the item should be added' do
83 | before { allow(item_container).to receive_messages(should_add_item?: true) }
84 |
85 | it 'converts it to an Item and adds it to the items collection' do
86 | item_container.items = items
87 | expect(item_container.items).to include(real_item)
88 | end
89 | end
90 |
91 | context 'when the item should not be added' do
92 | before { allow(item_container).to receive_messages(should_add_item?: false) }
93 |
94 | it "doesn't add it to the items collection" do
95 | item_container.items = items
96 | expect(item_container.items).not_to include(real_item)
97 | end
98 | end
99 | end
100 |
101 | describe '#selected?' do
102 | let(:item_1) { double(:item, selected?: false) }
103 | let(:item_2) { double(:item, selected?: false) }
104 |
105 | before do
106 | item_container.instance_variable_set(:@items, [item_1, item_2])
107 | end
108 |
109 | context 'when no item is selected' do
110 | it 'returns nil' do
111 | expect(item_container).not_to be_selected
112 | end
113 | end
114 |
115 | context 'when an item is selected' do
116 | it 'returns true' do
117 | allow(item_1).to receive_messages(selected?: true)
118 | expect(item_container).to be_selected
119 | end
120 | end
121 | end
122 |
123 | describe '#selected_item' do
124 | let(:item_1) { double(:item, selected?: false) }
125 | let(:item_2) { double(:item, selected?: false) }
126 |
127 | before(:each) do
128 | allow(SimpleNavigation).to receive_messages(current_navigation_for: :nav)
129 | allow(item_container).to receive_messages(:[] => nil)
130 | item_container.instance_variable_set(:@items, [item_1, item_2])
131 | end
132 |
133 | context "when navigation isn't explicitely set" do
134 | context 'and no item is selected' do
135 | it 'returns nil' do
136 | expect(item_container.selected_item).to be_nil
137 | end
138 | end
139 |
140 | context 'and an item selected' do
141 | before { allow(item_1).to receive_messages(selected?: true) }
142 |
143 | it 'returns the selected item' do
144 | expect(item_container.selected_item).to be item_1
145 | end
146 | end
147 | end
148 | end
149 |
150 | describe '#active_item_container_for' do
151 | context "when the desired level is the same as the container's" do
152 | it 'returns the container itself' do
153 | expect(item_container.active_item_container_for(1)).to be item_container
154 | end
155 | end
156 |
157 | context "when the desired level is different than the container's" do
158 | context 'and no subnavigation is selected' do
159 | before { allow(item_container).to receive_messages(selected_sub_navigation?: false) }
160 |
161 | it 'returns nil' do
162 | expect(item_container.active_item_container_for(2)).to be_nil
163 | end
164 | end
165 |
166 | context 'and a subnavigation is selected' do
167 | let(:sub_navigation) { double(:sub_navigation) }
168 | let(:selected_item) { double(:selected_item) }
169 |
170 | before do
171 | allow(item_container).to \
172 | receive_messages(selected_sub_navigation?: true, selected_item: selected_item)
173 | allow(selected_item).to receive_messages(sub_navigation: sub_navigation)
174 | end
175 |
176 | it 'calls recursively on the sub_navigation' do
177 | expect(sub_navigation).to receive(:active_item_container_for)
178 | .with(2)
179 | item_container.active_item_container_for(2)
180 | end
181 | end
182 | end
183 | end
184 |
185 | describe '#active_leaf_container' do
186 | context 'when the current container has a selected subnavigation' do
187 | let(:sub_navigation) { double(:sub_navigation) }
188 | let(:selected_item) { double(:selected_item) }
189 |
190 | before do
191 | allow(item_container).to receive_messages(selected_sub_navigation?: true,
192 | selected_item: selected_item)
193 | allow(selected_item).to receive_messages(sub_navigation: sub_navigation)
194 | end
195 |
196 | it 'calls recursively on the sub_navigation' do
197 | expect(sub_navigation).to receive(:active_leaf_container)
198 | item_container.active_leaf_container
199 | end
200 | end
201 |
202 | context 'when the current container is the leaf already' do
203 | before { allow(item_container).to receive_messages(selected_sub_navigation?: false) }
204 |
205 | it 'returns itsself' do
206 | expect(item_container.active_leaf_container).to be item_container
207 | end
208 | end
209 | end
210 |
211 | describe '#item' do
212 | let(:options) { Hash.new }
213 | let(:item) { double(:item) }
214 |
215 | context 'when a block is given' do
216 | let(:block) { proc{} }
217 | let(:sub_container) { double(:sub_container) }
218 |
219 | it 'yields a new ItemContainer' do
220 | allow_any_instance_of(Item).to \
221 | receive_messages(sub_navigation: sub_container)
222 |
223 | expect{ |blk|
224 | item_container.item('key', 'name', 'url', options, &blk)
225 | }.to yield_with_args(sub_container)
226 | end
227 |
228 | it "creates a new Item with the given params and block" do
229 | allow(Item).to receive(:new)
230 | .with(item_container, 'key', 'name', 'url', options, &block)
231 | .and_return(item)
232 | item_container.item('key', 'name', 'url', options, &block)
233 | expect(item_container.items).to include(item)
234 | end
235 |
236 | it 'adds the created item to the list of items' do
237 | item_container.item('key', 'name', 'url', options) {}
238 | expect(item_container.items).not_to include(item)
239 | end
240 | end
241 |
242 | context 'when no block is given' do
243 | it 'creates a new Item with the given params and no sub navigation' do
244 | allow(Item).to receive(:new)
245 | .with(item_container, 'key', 'name', 'url', options)
246 | .and_return(item)
247 | item_container.item('key', 'name', 'url', options)
248 | expect(item_container.items).to include(item)
249 | end
250 |
251 | it 'adds the created item to the list of items' do
252 | allow(Item).to receive_messages(new: item)
253 | item_container.item('key', 'name', 'url', options) {}
254 | expect(item_container.items).to include(item)
255 | end
256 | end
257 |
258 | describe 'Optional url and optional options' do
259 | context 'when item specifed without url or options' do
260 | it_behaves_like 'adding the item to the list' do
261 | let(:args) { ['key', 'name'] }
262 | end
263 | end
264 |
265 | context 'when item is specified with only a url' do
266 | it_behaves_like 'adding the item to the list' do
267 | let(:args) { ['key', 'name', 'url'] }
268 | end
269 | end
270 |
271 | context 'when item is specified with only options' do
272 | context 'and options do not contain any condition' do
273 | it_behaves_like 'adding the item to the list' do
274 | let(:args) { ['key', 'name', { option: true }] }
275 | end
276 | end
277 |
278 | context 'and options contains a negative condition' do
279 | it_behaves_like 'not adding the item to the list' do
280 | let(:args) { ['key', 'name', nil, { if: ->{ false }, option: true }] }
281 | end
282 | end
283 |
284 | context 'and options contains a positive condition' do
285 | it_behaves_like 'adding the item to the list' do
286 | let(:args) { ['key', 'name', nil, { if: ->{ true }, option: true }] }
287 | end
288 | end
289 | end
290 |
291 | context 'when item is specified with a url and options' do
292 | context 'and options do not contain any condition' do
293 | it_behaves_like 'adding the item to the list' do
294 | let(:args) { ['key', 'name', 'url', { option: true }] }
295 | end
296 | end
297 |
298 | context 'and options contains a negative condition' do
299 | it_behaves_like 'not adding the item to the list' do
300 | let(:args) { ['key', 'name', 'url', { if: ->{ false }, option: true }] }
301 | end
302 | end
303 |
304 | context 'and options contains a positive condition' do
305 | it_behaves_like 'adding the item to the list' do
306 | let(:args) { ['key', 'name', 'url', { if: ->{ true }, option: true }] }
307 | end
308 | end
309 | end
310 |
311 | context 'when a frozen options hash is given' do
312 | let(:options) do
313 | { html: { id: 'test' } }.freeze
314 | end
315 |
316 | it 'does not raise an exception' do
317 | expect{
318 | item_container.item('key', 'name', 'url', options)
319 | }.not_to raise_error
320 | end
321 | end
322 |
323 | describe "container options" do
324 | before do
325 | allow(item_container).to receive_messages(should_add_item?: add_item)
326 | item_container.item :key, 'name', 'url', options
327 | end
328 |
329 | context 'when the container :id option is specified' do
330 | let(:options) {{ container: { id: 'c_id' } }}
331 |
332 | context 'and the item should be added' do
333 | let(:add_item) { true }
334 |
335 | it 'changes its dom_id' do
336 | expect(item_container.dom_id).to eq 'c_id'
337 | end
338 | end
339 |
340 | context "and the item shouldn't be added" do
341 | let(:add_item) { false }
342 |
343 | it "doesn't change its dom_id" do
344 | expect(item_container.dom_id).to be_nil
345 | end
346 | end
347 | end
348 |
349 | context 'when the container :class option is specified' do
350 | let(:options) {{ container: { class: 'c_class' } }}
351 |
352 | context 'and the item should be added' do
353 | let(:add_item) { true }
354 |
355 | it 'changes its dom_class' do
356 | expect(item_container.dom_class).to eq 'c_class'
357 | end
358 | end
359 |
360 | context "and the item shouldn't be added" do
361 | let(:add_item) { false }
362 |
363 | it "doesn't change its dom_class" do
364 | expect(item_container.dom_class).to be_nil
365 | end
366 | end
367 | end
368 |
369 | context 'when the container :attributes option is specified' do
370 | let(:options) {{ container: { attributes: { option: true } } }}
371 |
372 | context 'and the item should be added' do
373 | let(:add_item) { true }
374 |
375 | it 'changes its dom_attributes' do
376 | expect(item_container.dom_attributes).to eq(option: true)
377 | end
378 | end
379 |
380 | context "and the item shouldn't be added" do
381 | let(:add_item) { false }
382 |
383 | it "doesn't change its dom_attributes" do
384 | expect(item_container.dom_attributes).to eq({})
385 | end
386 | end
387 | end
388 |
389 | context 'when the container :selected_class option is specified' do
390 | let(:options) {{ container: { selected_class: 'sel_class' } }}
391 |
392 | context 'and the item should be added' do
393 | let(:add_item) { true }
394 |
395 | it 'changes its selected_class' do
396 | expect(item_container.selected_class).to eq 'sel_class'
397 | end
398 | end
399 |
400 | context "and the item shouldn't be added" do
401 | let(:add_item) { false }
402 |
403 | it "doesn't change its selected_class" do
404 | expect(item_container.selected_class).to be_nil
405 | end
406 | end
407 | end
408 | end
409 | end
410 |
411 | describe 'Conditions' do
412 | context 'when an :if option is given' do
413 | let(:options) {{ if: proc{condition} }}
414 | let(:condition) { nil }
415 |
416 | context 'and it evals to true' do
417 | let(:condition) { true }
418 |
419 | it 'creates a new Item' do
420 | expect(Item).to receive(:new)
421 | item_container.item('key', 'name', 'url', options)
422 | end
423 | end
424 |
425 | context 'and it evals to false' do
426 | let(:condition) { false }
427 |
428 | it "doesn't create a new Item" do
429 | expect(Item).not_to receive(:new)
430 | item_container.item('key', 'name', 'url', options)
431 | end
432 | end
433 |
434 | context 'and it is not a proc or a method' do
435 | it 'raises an error' do
436 | expect{
437 | item_container.item('key', 'name', 'url', { if: 'text' })
438 | }.to raise_error(ArgumentError, ':if or :unless must be procs or lambdas')
439 | end
440 | end
441 | end
442 |
443 | context 'when an :unless option is given' do
444 | let(:options) {{ unless: proc{condition} }}
445 | let(:condition) { nil }
446 |
447 | context 'and it evals to false' do
448 | let(:condition) { false }
449 |
450 | it 'creates a new Navigation-Item' do
451 | expect(Item).to receive(:new)
452 | item_container.item('key', 'name', 'url', options)
453 | end
454 | end
455 |
456 | context 'and it evals to true' do
457 | let(:condition) { true }
458 |
459 | it "doesn't create a new Navigation-Item" do
460 | expect(Item).not_to receive(:new)
461 | item_container.item('key', 'name', 'url', options)
462 | end
463 | end
464 | end
465 | end
466 | end
467 |
468 | describe '#[]' do
469 | before do
470 | item_container.item(:first, 'first', 'bla')
471 | item_container.item(:second, 'second', 'bla')
472 | item_container.item(:third, 'third', 'bla')
473 | end
474 |
475 | it 'returns the item with the specified navi_key' do
476 | expect(item_container[:second].name).to eq 'second'
477 | end
478 |
479 | context 'when no item exists for the specified navi_key' do
480 | it 'returns nil' do
481 | expect(item_container[:invalid]).to be_nil
482 | end
483 | end
484 | end
485 |
486 | describe '#render' do
487 | # TODO
488 | let(:renderer_instance) { double(:renderer).as_null_object }
489 | let(:renderer_class) { double(:renderer_class, new: renderer_instance) }
490 |
491 | context 'when renderer is specified as an option' do
492 | context 'and is specified as a class' do
493 | it 'instantiates the passed renderer_class with the options' do
494 | expect(renderer_class).to receive(:new)
495 | .with(renderer: renderer_class)
496 | item_container.render(renderer: renderer_class)
497 | end
498 |
499 | it 'calls render on the renderer and passes self' do
500 | expect(renderer_instance).to receive(:render).with(item_container)
501 | item_container.render(renderer: renderer_class)
502 | end
503 | end
504 |
505 | context 'and is specified as a symbol' do
506 | before do
507 | SimpleNavigation.registered_renderers = {
508 | my_renderer: renderer_class
509 | }
510 | end
511 |
512 | it "instantiates the passed renderer_class with the options" do
513 | expect(renderer_class).to receive(:new).with(renderer: :my_renderer)
514 | item_container.render(renderer: :my_renderer)
515 | end
516 |
517 | it 'calls render on the renderer and passes self' do
518 | expect(renderer_instance).to receive(:render).with(item_container)
519 | item_container.render(renderer: :my_renderer)
520 | end
521 | end
522 | end
523 |
524 | context 'when no renderer is specified' do
525 | let(:options) { Hash.new }
526 |
527 | before { allow(item_container).to receive_messages(renderer: renderer_class) }
528 |
529 | it "instantiates the container's renderer with the options" do
530 | expect(renderer_class).to receive(:new).with(options)
531 | item_container.render(options)
532 | end
533 |
534 | it 'calls render on the renderer and passes self' do
535 | expect(renderer_instance).to receive(:render).with(item_container)
536 | item_container.render(options)
537 | end
538 | end
539 | end
540 |
541 | describe '#renderer' do
542 | context 'when no renderer is set explicitly' do
543 | it 'returns globally-configured renderer' do
544 | expect(item_container.renderer).to be Configuration.instance.renderer
545 | end
546 | end
547 |
548 | context 'when a renderer is set explicitly' do
549 | let(:renderer) { double(:renderer) }
550 |
551 | before { item_container.renderer = renderer }
552 |
553 | it 'returns the specified renderer' do
554 | expect(item_container.renderer).to be renderer
555 | end
556 | end
557 | end
558 |
559 | describe '#level_for_item' do
560 | before(:each) do
561 | item_container.item(:p1, 'p1', 'p1')
562 | item_container.item(:p2, 'p2', 'p2') do |p2|
563 | p2.item(:s1, 's1', 's1')
564 | p2.item(:s2, 's2', 's2') do |s2|
565 | s2.item(:ss1, 'ss1', 'ss1')
566 | s2.item(:ss2, 'ss2', 'ss2')
567 | end
568 | p2.item(:s3, 's3', 's3')
569 | end
570 | item_container.item(:p3, 'p3', 'p3')
571 | end
572 |
573 | shared_examples 'returning the level of an item' do |item, level|
574 | specify{ expect(item_container.level_for_item(item)).to eq level }
575 | end
576 |
577 | it_behaves_like 'returning the level of an item', :p1, 1
578 | it_behaves_like 'returning the level of an item', :p3, 1
579 | it_behaves_like 'returning the level of an item', :s1, 2
580 | it_behaves_like 'returning the level of an item', :ss1, 3
581 | it_behaves_like 'returning the level of an item', :x, nil
582 | end
583 |
584 | describe '#empty?' do
585 | context 'when there are no items' do
586 | it 'returns true' do
587 | item_container.instance_variable_set(:@items, [])
588 | expect(item_container).to be_empty
589 | end
590 | end
591 |
592 | context 'when there are some items' do
593 | it 'returns false' do
594 | item_container.instance_variable_set(:@items, [double(:item)])
595 | expect(item_container).not_to be_empty
596 | end
597 | end
598 | end
599 | end
600 | end
601 |
--------------------------------------------------------------------------------
/spec/simple_navigation/item_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | describe Item do
3 | let!(:item_container) { ItemContainer.new }
4 |
5 | let(:adapter) { double(:adapter) }
6 | let(:item_args) { [item_container, :my_key, 'name', url, options] }
7 | let(:item) { Item.new(*item_args) }
8 | let(:options) { Hash.new }
9 | let(:url) { 'url' }
10 |
11 | before { allow(SimpleNavigation).to receive_messages(adapter: adapter) }
12 |
13 | describe '#highlights_on' do
14 | let(:options) {{ highlights_on: :test }}
15 |
16 | it "returns the item's highlights_on option" do
17 | expect(item.highlights_on).to eq :test
18 | end
19 | end
20 |
21 | describe '#initialize' do
22 | context 'when there is a sub_navigation' do
23 | let(:subnav_container) { double(:subnav_container).as_null_object }
24 |
25 | shared_examples 'creating sub navigation container' do
26 | it 'creates a sub navigation container with a level+1' do
27 | expect(item.sub_navigation.level).to eq 2
28 | end
29 | end
30 |
31 | context 'when a block is given' do
32 | it_behaves_like 'creating sub navigation container' do
33 | let(:item) { Item.new(*item_args) {} }
34 | end
35 |
36 | it 'calls the block' do
37 | allow(ItemContainer).to receive_messages(new: subnav_container)
38 |
39 | expect{ |blk|
40 | Item.new(*item_args, &blk)
41 | }.to yield_with_args(subnav_container)
42 | end
43 | end
44 |
45 | context 'when no block is given' do
46 | context 'and items are given' do
47 | let(:items) { [] }
48 | let(:options) {{ items: items }}
49 |
50 | it_behaves_like 'creating sub navigation container'
51 |
52 | it "sets the items on the subnav_container" do
53 | expect(item.sub_navigation.items).to eq items
54 | end
55 | end
56 |
57 | context 'and no items are given' do
58 | it "doesn't create a new ItemContainer" do
59 | item = Item.new(*item_args)
60 | expect(item.sub_navigation).to be_nil
61 | end
62 | end
63 | end
64 | end
65 |
66 | context 'when a :method option is given' do
67 | let(:options) {{ method: :delete }}
68 |
69 | it "sets the item's method" do
70 | expect(item.method).to eq :delete
71 | end
72 | end
73 |
74 | context 'when no :method option is given' do
75 | it "sets the item's method to nil" do
76 | expect(item.method).to be_nil
77 | end
78 | end
79 |
80 | context 'when an :highlights_on option is given' do
81 | let(:highlights_on) { double(:highlights_on) }
82 | let(:options) {{ highlights_on: highlights_on }}
83 |
84 | it "sets the item's highlights_on" do
85 | expect(item.highlights_on).to eq highlights_on
86 | end
87 | end
88 |
89 | context 'when no :highlights_on option is given' do
90 | it "sets the item's highlights_on to nil" do
91 | expect(item.highlights_on).to be_nil
92 | end
93 | end
94 |
95 | context 'when a url is given' do
96 | context 'and it is a string' do
97 | it "sets the item's url accordingly" do
98 | expect(item.url).to eq 'url'
99 | end
100 | end
101 |
102 | context 'and it is a proc' do
103 | let(:url) { proc{ "my_" + "url" } }
104 |
105 | it "sets the item's url accordingly" do
106 | expect(item.url).to eq 'my_url'
107 | end
108 | end
109 |
110 | context 'and it is nil' do
111 | let(:url) { nil }
112 |
113 | it "sets the item's url accordingly" do
114 | expect(item.url).to be_nil
115 | end
116 | end
117 | end
118 |
119 | context 'when no url nor options is specified' do
120 | let(:item_args) { [item_container, :my_key, 'name'] }
121 |
122 | it "sets the item's url to nil" do
123 | expect(item.url).to be_nil
124 | end
125 | end
126 |
127 | context 'when only a url is given' do
128 | let(:item_args) { [item_container, :my_key, 'name', 'url'] }
129 |
130 | it "set the item's url accordingly" do
131 | expect(item.url).to eq 'url'
132 | end
133 | end
134 |
135 | context 'when url and options are given' do
136 | let(:options) {{ html: { option: true } }}
137 |
138 | before { allow(adapter).to receive_messages(current_page?: false) }
139 |
140 | it "set the item's url accordingly" do
141 | expect(item.url).to eq 'url'
142 | end
143 |
144 | it "sets the item's html_options accordingly" do
145 | allow(item).to \
146 | receive_messages(selected_by_subnav?: false,
147 | selected_by_condition?: false)
148 | expect(item.html_options).to include(option: true)
149 | end
150 | end
151 | end
152 |
153 | describe '#link_html_options' do
154 | let(:options) {{ link_html: :test }}
155 |
156 | it "returns the item's link_html option" do
157 | expect(item.link_html_options).to eq :test
158 | end
159 | end
160 |
161 | describe '#method' do
162 | let(:options) {{ method: :test }}
163 |
164 | it "returns the item's method option" do
165 | expect(item.method).to eq :test
166 | end
167 | end
168 |
169 | describe '#name' do
170 | before do
171 | allow(SimpleNavigation.config).to \
172 | receive_messages(name_generator: proc{ |name| "#{name}" })
173 | end
174 |
175 | context 'when no option is given' do
176 | context 'and the name_generator uses only the name' do
177 | it 'uses the default name_generator' do
178 | expect(item.name).to eq 'name'
179 | end
180 | end
181 |
182 | context 'and the name_generator uses only the item itself' do
183 | before do
184 | allow(SimpleNavigation.config).to \
185 | receive_messages(name_generator: proc{ |name, item| "#{item.key}" })
186 | end
187 |
188 | it 'uses the default name_generator' do
189 | expect(item.name).to eq 'my_key'
190 | end
191 | end
192 | end
193 |
194 | context 'when the :apply_generator is false' do
195 | it "returns the item's name" do
196 | expect(item.name(apply_generator: false)).to eq 'name'
197 | end
198 | end
199 |
200 | context 'when a block is given' do
201 | let(:item_args) { [item_container, :my_key, -> { 'Name in block' }, url, options] }
202 |
203 | it "returns the item's name that is defined in the block" do
204 | expect(item.name).to include 'Name in block'
205 | end
206 | end
207 | end
208 |
209 | describe '#selected?' do
210 | context 'when the item has no :highlights_on option' do
211 | before { allow(SimpleNavigation).to receive_messages(config: config) }
212 |
213 | context 'and auto highlighting is off' do
214 | let(:config) { double(:config, auto_highlight: false) }
215 |
216 | it 'returns false' do
217 | expect(item.selected?).to be false
218 | end
219 | end
220 |
221 | context 'and auto highlighting is on' do
222 | let(:config) { double(:config, ignore_query_params_on_auto_highlight: true, ignore_anchors_on_auto_highlight: true, auto_highlight: true) }
223 |
224 | context "and the current url matches the item's url" do
225 | before { allow(adapter).to receive_messages(current_page?: true) }
226 |
227 | it 'returns true' do
228 | expect(item.selected?).to be true
229 | end
230 | end
231 |
232 | context "and the current url does not match the item's url" do
233 | let(:config) do
234 | double(:config, auto_highlight: false, highlight_on_subpath: false)
235 | end
236 |
237 | before { allow(adapter).to receive_messages(current_page?: false) }
238 |
239 | it 'returns false' do
240 | expect(item.selected?).to be false
241 | end
242 | end
243 |
244 | context 'and highlights_on_subpath is on' do
245 | let(:config) do
246 | double(:config, auto_highlight: true, highlight_on_subpath: true, ignore_query_params_on_auto_highlight: true, ignore_anchors_on_auto_highlight: true)
247 | end
248 |
249 | context "but item has no url" do
250 | let(:url) { nil }
251 |
252 | it 'returns false' do
253 | expect(item.selected?).to be false
254 | end
255 | end
256 |
257 | context "and the current url is a sub path of the item's url" do
258 | before do
259 | allow(adapter).to \
260 | receive_messages(current_page?: false, request_uri: 'url/test')
261 | end
262 |
263 | it 'returns true' do
264 | expect(item.selected?).to be true
265 | end
266 | end
267 |
268 | context "and the current url is not a sub path of the item's url" do
269 | before do
270 | allow(adapter).to \
271 | receive_messages(current_page?: false, request_uri: 'other/test')
272 | end
273 |
274 | it 'returns false' do
275 | expect(item.selected?).to be false
276 | end
277 | end
278 | end
279 | end
280 | end
281 |
282 | context 'when the item has a :highlights_on option' do
283 | context 'and it is a regular expression' do
284 | before { allow(adapter).to receive_messages(request_uri: '/test') }
285 |
286 | context 'and the current url matches the expression' do
287 | let(:options) {{ highlights_on: /test/ }}
288 |
289 | it 'returns true' do
290 | expect(item.selected?).to be true
291 | end
292 | end
293 |
294 | context 'and the current url does not match the expression' do
295 | let(:options) {{ highlights_on: /other/ }}
296 |
297 | it 'returns false' do
298 | expect(item.selected?).to be false
299 | end
300 | end
301 | end
302 |
303 | context 'and it is a callable object' do
304 | context 'and the call returns true' do
305 | let(:options) {{ highlights_on: -> { true } }}
306 |
307 | it 'returns true' do
308 | expect(item.selected?).to be true
309 | end
310 | end
311 |
312 | context 'and the call returns false' do
313 | let(:options) {{ highlights_on: -> { false } }}
314 |
315 | it 'returns false' do
316 | expect(item.selected?).to be false
317 | end
318 | end
319 | end
320 |
321 | context 'and it is the :subpath symbol' do
322 | let(:options) {{ highlights_on: :subpath }}
323 |
324 | context "and the current url is a sub path of the item's url" do
325 | before do
326 | allow(adapter).to receive_messages(request_uri: 'url/test')
327 | end
328 |
329 | it 'returns true' do
330 | expect(item.selected?).to be true
331 | end
332 | end
333 |
334 | context "and the current url is not a sub path of the item's url" do
335 | before do
336 | allow(adapter).to receive_messages(request_uri: 'other/test')
337 | end
338 |
339 | it 'returns false' do
340 | expect(item.selected?).to be false
341 | end
342 | end
343 | end
344 |
345 | context 'and it is non usable' do
346 | let(:options) {{ highlights_on: :hello }}
347 |
348 | it 'raises an exception' do
349 | expect{ item.selected? }.to raise_error(ArgumentError, ':highlights_on must be a Regexp, Proc or :subpath')
350 | end
351 | end
352 | end
353 | end
354 |
355 | describe '#selected_class' do
356 | context 'when the item is selected' do
357 | before { allow(item).to receive_messages(selected?: true) }
358 |
359 | it 'returns the default selected_class' do
360 | expect(item.selected_class).to eq 'selected'
361 | end
362 |
363 | context 'and selected_class is defined in the context' do
364 | before { allow(item_container).to receive_messages(selected_class: 'defined') }
365 |
366 | it "returns the context's selected_class" do
367 | expect(item.selected_class).to eq 'defined'
368 | end
369 | end
370 | end
371 |
372 | context 'when the item is not selected' do
373 | before { allow(item).to receive_messages(selected?: false) }
374 |
375 | it 'returns nil' do
376 | expect(item.selected_class).to be_nil
377 | end
378 | end
379 | end
380 |
381 | describe ':html_options argument' do
382 | let(:selected_classes) { 'selected simple-navigation-active-leaf' }
383 |
384 | context 'when the :class option is given' do
385 | let(:options) {{ html: { class: 'my_class' } }}
386 |
387 | context 'and the item is selected' do
388 | before { allow(item).to receive_messages(selected?: true, selected_by_condition?: true) }
389 |
390 | it "adds the specified class to the item's html classes" do
391 | expect(item.html_options[:class]).to include('my_class')
392 | end
393 |
394 | it "doesn't replace the default html classes of a selected item" do
395 | expect(item.html_options[:class]).to include(selected_classes)
396 | end
397 | end
398 |
399 | context "and the item isn't selected" do
400 | before { allow(item).to receive_messages(selected?: false, selected_by_condition?: false) }
401 |
402 | it "sets the specified class as the item's html classes" do
403 | expect(item.html_options[:class]).to include('my_class')
404 | end
405 | end
406 | end
407 |
408 | context "when the :class option isn't given" do
409 | context 'and the item is selected' do
410 | before { allow(item).to receive_messages(selected?: true, selected_by_condition?: true) }
411 |
412 | it "sets the default html classes of a selected item" do
413 | expect(item.html_options[:class]).to include(selected_classes)
414 | end
415 | end
416 |
417 | context "and the item isn't selected" do
418 | before { allow(item).to receive_messages(selected?: false, selected_by_condition?: false) }
419 |
420 | it "doesn't set any html class on the item" do
421 | expect(item.html_options[:class]).to be_blank
422 | end
423 | end
424 | end
425 |
426 | shared_examples 'generating id' do |id|
427 | it "sets the item's html id to the specified id" do
428 | expect(item.html_options[:id]).to eq id
429 | end
430 | end
431 |
432 | describe 'when the :id option is given' do
433 | let(:options) {{ html: { id: 'my_id' } }}
434 |
435 | before do
436 | allow(SimpleNavigation.config).to receive_messages(autogenerate_item_ids: generate_ids)
437 | allow(item).to receive_messages(selected?: false, selected_by_condition?: false)
438 | end
439 |
440 | context 'and :autogenerate_item_ids is true' do
441 | let(:generate_ids) { true }
442 |
443 | it_behaves_like 'generating id', 'my_id'
444 | end
445 |
446 | context 'and :autogenerate_item_ids is false' do
447 | let(:generate_ids) { false }
448 |
449 | it_behaves_like 'generating id', 'my_id'
450 | end
451 | end
452 |
453 | context "when the :id option isn't given" do
454 | before do
455 | allow(SimpleNavigation.config).to receive_messages(autogenerate_item_ids: generate_ids)
456 | allow(item).to receive_messages(selected?: false, selected_by_condition?: false)
457 | end
458 |
459 | context 'and :autogenerate_item_ids is true' do
460 | let(:generate_ids) { true }
461 |
462 | it_behaves_like 'generating id', 'my_key'
463 | end
464 |
465 | context 'and :autogenerate_item_ids is false' do
466 | let(:generate_ids) { false }
467 |
468 | it "doesn't set any html id on the item" do
469 | expect(item.html_options[:id]).to be_blank
470 | end
471 | end
472 | end
473 | end
474 | end
475 | end
476 |
--------------------------------------------------------------------------------
/spec/simple_navigation/items_provider_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | describe ItemsProvider do
3 | let(:items_provider) { ItemsProvider.new(provider) }
4 |
5 | describe '#items' do
6 | let(:items) { double(:items) }
7 |
8 | context 'when provider is a symbol' do
9 | let(:context) { double(:context, provider_method: items) }
10 | let(:provider) { :provider_method }
11 |
12 | before { allow(SimpleNavigation).to receive_messages(context_for_eval: context) }
13 |
14 | it 'retrieves the items from the evaluation context' do
15 | expect(items_provider.items).to eq items
16 | end
17 | end
18 |
19 | context 'when provider responds to :items' do
20 | let(:provider) { double(:provider, items: items) }
21 |
22 | it 'retrieves the items from the provider object' do
23 | expect(items_provider.items).to eq items
24 | end
25 | end
26 |
27 | context 'provider is a collection' do
28 | let(:provider) { [] }
29 |
30 | it 'retrieves the items by returning the provider' do
31 | expect(items_provider.items).to eq provider
32 | end
33 | end
34 |
35 | context 'when provider is something else' do
36 | let(:provider) { double(:provider) }
37 |
38 | it 'raises an exception' do
39 | expect{ items_provider.items }.to raise_error(RuntimeError, /items_provider either must be a symbol .*, an object .* or an enumerable/)
40 | end
41 | end
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/simple_navigation/renderer/base_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | module Renderer
3 | describe Base do
4 | subject(:base) { Base.new(options) }
5 |
6 | let(:adapter) { double(:adapter) }
7 | let(:options) { Hash.new }
8 |
9 | before { allow(SimpleNavigation).to receive_messages(adapter: adapter) }
10 |
11 | it 'delegates the :link_to method to adapter' do
12 | allow(adapter).to receive_messages(link_to: 'link_to')
13 | expect(base.link_to).to eq 'link_to'
14 | end
15 |
16 | it 'delegates the :content_tag method to adapter' do
17 | allow(adapter).to receive_messages(content_tag: 'content_tag')
18 | expect(base.content_tag).to eq 'content_tag'
19 | end
20 |
21 | describe '#initialize' do
22 | it "sets the renderer adapter to the SimpleNavigation one" do
23 | expect(base.adapter).to be adapter
24 | end
25 | end
26 |
27 | describe '#options' do
28 | it "returns the renderer's options" do
29 | expect(base.options).to be options
30 | end
31 | end
32 |
33 | describe '#render' do
34 | it "raise an exception to indicate it's a subclass responsibility" do
35 | expect{ base.render(:container) }.to raise_error(NotImplementedError, 'subclass responsibility')
36 | end
37 | end
38 |
39 | describe '#expand_all?' do
40 | context 'when :options is set' do
41 | context 'and the :expand_all option is true' do
42 | let(:options) {{ expand_all: true }}
43 |
44 | it 'returns true' do
45 | expect(base.expand_all?).to be true
46 | end
47 | end
48 |
49 | context 'and the :expand_all option is false' do
50 | let(:options) {{ expand_all: false }}
51 |
52 | it 'returns false' do
53 | expect(base.expand_all?).to be false
54 | end
55 | end
56 | end
57 |
58 | context "when :options isn't set" do
59 | let(:options) { Hash.new }
60 |
61 | it 'returns false' do
62 | expect(base.expand_all?).to be false
63 | end
64 | end
65 | end
66 |
67 | describe '#skip_if_empty?' do
68 | context 'when :options is set' do
69 | context 'and the :skip_if_empty option is true' do
70 | let(:options) {{ skip_if_empty: true }}
71 |
72 | it 'returns true' do
73 | expect(base.skip_if_empty?).to be true
74 | end
75 | end
76 |
77 | context 'and the :skip_if_empty option is false' do
78 | let(:options) {{ skip_if_empty: false }}
79 |
80 | it 'returns true' do
81 | expect(base.skip_if_empty?).to be false
82 | end
83 | end
84 | end
85 |
86 | context "when :options isn't set" do
87 | let(:options) { Hash.new }
88 |
89 | it 'returns true' do
90 | expect(base.skip_if_empty?).to be false
91 | end
92 | end
93 | end
94 |
95 | describe '#level' do
96 | context 'and the :level option is set' do
97 | let(:options) {{ level: 1 }}
98 |
99 | it 'returns the specified level' do
100 | expect(base.level).to eq 1
101 | end
102 | end
103 |
104 | context "and the :level option isn't set" do
105 | let(:options) { Hash.new }
106 |
107 | it 'returns :all' do
108 | expect(base.level).to eq :all
109 | end
110 | end
111 | end
112 |
113 | describe '#consider_sub_navigation?' do
114 | let(:item) { double(:item) }
115 |
116 | before { allow(item).to receive_messages(sub_navigation: sub_navigation) }
117 |
118 | context 'when the item has no sub navigation' do
119 | let(:sub_navigation) { nil }
120 |
121 | it 'returns false' do
122 | expect(base.send(:consider_sub_navigation?, item)).to be false
123 | end
124 | end
125 |
126 | context 'when the item has sub navigation' do
127 | let(:sub_navigation) { double(:sub_navigation) }
128 |
129 | context 'and the renderer has an unknown level' do
130 | before { allow(base).to receive_messages(level: 'unknown') }
131 |
132 | it 'returns false' do
133 | expect(base.send(:consider_sub_navigation?, item)).to be false
134 | end
135 | end
136 |
137 | context 'and the renderer has a level set to :all' do
138 | before { allow(base).to receive_messages(level: :all) }
139 |
140 | it 'returns false' do
141 | expect(base.send(:consider_sub_navigation?, item)).to be true
142 | end
143 | end
144 |
145 | context "when the renderer's level is a number" do
146 | before { allow(base).to receive_messages(level: 2) }
147 |
148 | it 'returns false' do
149 | expect(base.send(:consider_sub_navigation?, item)).to be false
150 | end
151 | end
152 |
153 | context "when the renderer's level is a Range" do
154 | before { allow(base).to receive_messages(level: 2..3) }
155 |
156 | context "and sub navigation's level is greater than range.max" do
157 | before { allow(sub_navigation).to receive_messages(level: 4) }
158 |
159 | it 'returns false' do
160 | expect(base.send(:consider_sub_navigation?, item)).to be false
161 | end
162 | end
163 |
164 | context "and sub navigation's level is equal to range.max" do
165 | before { allow(sub_navigation).to receive_messages(level: 3) }
166 |
167 | it 'returns true' do
168 | expect(base.send(:consider_sub_navigation?, item)).to be true
169 | end
170 | end
171 |
172 | context "and sub navigation's level is equal to range.min" do
173 | before { allow(sub_navigation).to receive_messages(level: 2) }
174 |
175 | it 'returns true' do
176 | expect(base.send(:consider_sub_navigation?, item)).to be true
177 | end
178 | end
179 | end
180 | end
181 | end
182 |
183 | describe '#include_sub_navigation?' do
184 | let(:item) { double(:item) }
185 |
186 | context 'when consider_sub_navigation? is true' do
187 | before { allow(base).to receive_messages(consider_sub_navigation?: true) }
188 |
189 | context 'and expand_sub_navigation? is true' do
190 | before { allow(base).to receive_messages(expand_sub_navigation?: true) }
191 |
192 | it 'returns true' do
193 | expect(base.include_sub_navigation?(item)).to be true
194 | end
195 | end
196 |
197 | context 'and expand_sub_navigation? is false' do
198 | before { allow(base).to receive_messages(expand_sub_navigation?: false) }
199 |
200 | it 'returns false' do
201 | expect(base.include_sub_navigation?(item)).to be false
202 | end
203 | end
204 | end
205 |
206 | context 'consider_sub_navigation? is false' do
207 | before { allow(base).to receive_messages(consider_sub_navigation?: false) }
208 |
209 | context 'and expand_sub_navigation? is true' do
210 | before { allow(base).to receive_messages(expand_sub_navigation?: true) }
211 |
212 | it 'returns false' do
213 | expect(base.include_sub_navigation?(item)).to be false
214 | end
215 | end
216 |
217 | context 'and expand_sub_navigation? is false' do
218 | before { allow(base).to receive_messages(expand_sub_navigation?: false) }
219 |
220 | it 'returns false' do
221 | expect(base.include_sub_navigation?(item)).to be false
222 | end
223 | end
224 | end
225 | end
226 |
227 | describe '#render_sub_navigation_for' do
228 | let(:item) { double(:item, sub_navigation: sub_navigation) }
229 | let(:sub_navigation) { double(:sub_navigation) }
230 |
231 | it 'renders the sub navigation passing it the options' do
232 | expect(sub_navigation).to receive(:render).with(options)
233 | base.render_sub_navigation_for(item)
234 | end
235 | end
236 | end
237 | end
238 | end
239 |
--------------------------------------------------------------------------------
/spec/simple_navigation/renderer/breadcrumbs_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | module Renderer
3 | describe Breadcrumbs do
4 | let!(:navigation) { setup_navigation('nav_id', 'nav_class') }
5 |
6 | let(:item) { nil }
7 | let(:options) {{ level: :all }}
8 | let(:output) { renderer.render(navigation) }
9 | let(:renderer) { Breadcrumbs.new(options) }
10 |
11 | before { select_an_item(navigation[item]) if item }
12 |
13 | describe '#render' do
14 | it "renders a 'div' tag for the navigation" do
15 | expect(output).to have_css('div')
16 | end
17 |
18 | it "sets the right html id on the rendered 'div' tag" do
19 | expect(output).to have_css('div#nav_id')
20 | end
21 |
22 | it "sets the right html classes on the rendered 'div' tag" do
23 | expect(output).to have_css('div.nav_class')
24 | end
25 |
26 | context 'when no item is selected' do
27 | it "doesn't render any 'a' tag in the 'div' tag" do
28 | expect(output).not_to have_css('div a')
29 | end
30 | end
31 |
32 | context 'when an item is selected' do
33 | let(:item) { :invoices }
34 |
35 | it "renders the selected 'a' tag" do
36 | expect(output).to have_css('div a')
37 | end
38 |
39 | it "remders the 'a' tag without any html id" do
40 | expect(output).not_to have_css('div a[id]')
41 | end
42 |
43 | it "renders the 'a' tag without any html class" do
44 | expect(output).not_to have_css('div a[class]')
45 | end
46 |
47 | context 'and the :allow_classes_and_ids option is true' do
48 | let(:options) {{ level: :all, allow_classes_and_ids: true }}
49 |
50 | it "renders the 'a' tag with the selected class" do
51 | expect(output).to have_css('div a.selected')
52 | end
53 |
54 | context "and the item hasn't any id explicitly set" do
55 | it "renders the 'a' tag without any html id" do
56 | expect(output).not_to have_css('div a[id]')
57 | end
58 | end
59 |
60 | context 'and the item has an explicitly set id' do
61 | let(:item) { :users }
62 |
63 | it "renders the 'a' tag with an html id" do
64 | expect(output).to have_css('div a#breadcrumb_users_link_id')
65 | end
66 | end
67 | end
68 | end
69 |
70 | context 'and the :prefix option is set' do
71 | let(:options) {{ prefix: 'You are here: ' }}
72 |
73 | context 'and there are no items to render' do
74 | let(:item) { nil }
75 |
76 | it "doesn't render the prefix before the breadcrumbs" do
77 | expect(output).not_to match(/^You are here: /)
78 | end
79 | end
80 |
81 | context 'and there are items to render' do
82 | let(:item) { :invoices }
83 |
84 | it 'renders the prefix before the breadcrumbs' do
85 | expect(output).to match(/^You are here: /)
86 | end
87 | end
88 | end
89 |
90 | context 'when a sub navigation item is selected' do
91 | before do
92 | allow(navigation[:invoices]).to receive_messages(selected?: true)
93 |
94 | allow(navigation[:invoices].sub_navigation[:unpaid]).to \
95 | receive_messages(selected?: true, selected_by_condition?: true)
96 | end
97 |
98 | it 'renders all items as links' do
99 | expect(output).to have_css('div a', 2)
100 | end
101 |
102 | context 'when the :static_leaf option is true' do
103 | let(:options) {{ level: :all, static_leaf: true }}
104 |
105 | it 'renders the items as links' do
106 | expect(output).to have_css('div a')
107 | end
108 |
109 | it 'renders the last item as simple text' do
110 | expect(output).to have_css('div span')
111 | end
112 | end
113 | end
114 | end
115 | end
116 | end
117 | end
118 |
--------------------------------------------------------------------------------
/spec/simple_navigation/renderer/json_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | module Renderer
3 | describe Json do
4 | describe '#render' do
5 | let!(:navigation) { setup_navigation('nav_id', 'nav_class') }
6 |
7 | let(:item) { :invoices }
8 | let(:options) {{ level: :all }}
9 | let(:output) { renderer.render(navigation) }
10 | let(:parsed_output) { JSON.parse(output) }
11 | let(:renderer) { Json.new(options) }
12 |
13 | before { select_an_item(navigation[item]) if item }
14 |
15 | context 'when an item is selected' do
16 |
17 | it 'renders the selected page' do
18 | invoices_item = parsed_output.find { |item| item['name'] == 'Invoices' }
19 | expect(invoices_item).to include('selected' => true)
20 | end
21 | end
22 |
23 | context 'when the :as_hash option is true' do
24 | let(:options) {{ level: :all, as_hash: true }}
25 |
26 | it 'returns every item as a hash' do
27 | expect(output).to be_an Array
28 |
29 | output.each do |item|
30 | expect(item).to be_an Hash
31 | end
32 | end
33 |
34 | it 'renders the selected page' do
35 | invoices_item = output.find { |item| item[:name] == 'Invoices' }
36 | expect(invoices_item).to include(selected: true)
37 | end
38 | end
39 |
40 | context 'with options' do
41 | it 'should render options for each item' do
42 | parsed_output.each do |item|
43 | expect(item).to have_key('options')
44 | end
45 | end
46 | end
47 |
48 | context 'when a sub navigation item is selected' do
49 | let(:invoices_item) do
50 | parsed_output.find { |item| item['name'] == 'Invoices' }
51 | end
52 | let(:unpaid_item) do
53 | invoices_item['items'].find { |item| item['name'] == 'Unpaid' }
54 | end
55 |
56 | before do
57 | allow(navigation[:invoices]).to receive_messages(selected?: true)
58 |
59 | allow(navigation[:invoices].sub_navigation[:unpaid]).to \
60 | receive_messages(selected?: true, selected_by_condition?: true)
61 | end
62 |
63 | it 'marks all the parent items as selected' do
64 | expect(invoices_item).to include('selected' => true)
65 | end
66 |
67 | it 'marks the item as selected' do
68 | expect(unpaid_item).to include('selected' => true)
69 | end
70 | end
71 | end
72 | end
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/spec/simple_navigation/renderer/links_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | module Renderer
3 | describe Links do
4 | describe '#render' do
5 | let!(:navigation) { setup_navigation('nav_id', 'nav_class') }
6 |
7 | let(:item) { nil }
8 | let(:options) { { level: :all } }
9 | let(:output) { renderer.render(navigation) }
10 | let(:renderer) { Links.new(options) }
11 |
12 | before { select_an_item(navigation[item]) if item }
13 |
14 | it "renders a 'div' tag for the navigation" do
15 | expect(output).to have_css('div')
16 | end
17 |
18 | it "sets the right html id on the rendered 'div' tag" do
19 | expect(output).to have_css('div#nav_id')
20 | end
21 |
22 | it "sets the right html classes on the rendered 'div' tag" do
23 | expect(output).to have_css('div.nav_class')
24 | end
25 |
26 | it "renders an 'a' tag for each item" do
27 | expect(output).to have_css('a', 3)
28 | end
29 |
30 | it "renders the 'a' tags with the corresponding item's :html_options" do
31 | expect(output).to have_css('a[style="float:right"]')
32 | end
33 |
34 | context 'when an item has a specified id' do
35 | it "renders the 'a' tags with the specified id" do
36 | expect(output).to have_css('a#users_id')
37 | end
38 | end
39 |
40 | context 'when an item has no specified id' do
41 | it "uses a default id by stringifying the item's key" do
42 | expect(output).to have_css('a#invoices')
43 | end
44 | end
45 |
46 | context 'when no item is selected' do
47 | it "renders items without the 'selected' class" do
48 | expect(output).not_to have_css('a.selected')
49 | end
50 | end
51 |
52 | context 'when an item is selected' do
53 | let(:item) { :invoices }
54 |
55 | it "renders the selected item with the 'selected' class" do
56 | expect(output).to have_css('a#invoices.selected')
57 | end
58 | end
59 |
60 | context "when the :join_with option is set" do
61 | let(:options) {{ level: :all, join_with: ' | ' }}
62 |
63 | it 'separates the items with the specified separator' do
64 | expect(output.scan(' | ').size).to eq 3
65 | end
66 | end
67 |
68 | context 'when a sub navigation item is selected' do
69 | before do
70 | allow(navigation[:invoices]).to receive_messages(selected?: true)
71 |
72 | allow(navigation[:invoices].sub_navigation[:unpaid]).to \
73 | receive_messages(selected?: true, selected_by_condition?: true)
74 | end
75 |
76 | it 'renders the main parent as selected' do
77 | expect(output).to have_css('a#invoices.selected')
78 | end
79 |
80 | it "doesn't render the nested item's link" do
81 | expect(output).not_to have_css('a#unpaid')
82 | end
83 | end
84 | end
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/spec/simple_navigation/renderer/list_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | module Renderer
3 | describe List do
4 | let!(:navigation) { setup_navigation('nav_id', 'nav_class') }
5 |
6 | let(:item) { nil }
7 | let(:options) { { level: :all } }
8 | let(:output) { renderer.render(navigation) }
9 | let(:renderer) { List.new(options) }
10 |
11 | before { select_an_item(navigation[item]) if item }
12 |
13 | describe '#render' do
14 | it "renders an 'ul' tag for the navigation" do
15 | expect(output).to have_css('ul')
16 | end
17 |
18 | it "sets the right html id on the rendered 'ul' tag" do
19 | expect(output).to have_css('ul#nav_id')
20 | end
21 |
22 | it "sets the right html classes on the rendered 'ul' tag" do
23 | expect(output).to have_css('ul.nav_class')
24 | end
25 |
26 | context 'when an item has no specified id' do
27 | it "renders the item's 'li' tag with the item's stingified key as id" do
28 | expect(output).to have_css('li#invoices')
29 | end
30 | end
31 |
32 | context 'when an item has a specified id' do
33 | it "renders the item's 'li' tag with the specified id" do
34 | expect(output).to have_css('li#users_id')
35 | end
36 | end
37 |
38 | context 'when no item is selected' do
39 | it "renders each item as 'li' tag without any selected class" do
40 | expect(output).not_to have_css('ul li.selected')
41 | end
42 |
43 | it "renders each item as 'a' tag without any selected class" do
44 | expect(output).not_to have_css('ul li a.selected')
45 | end
46 | end
47 |
48 | context 'when an item is selected' do
49 | let(:item) { :invoices }
50 |
51 | it "renders the item's 'li' tag with its id and selected classes" do
52 | expect(output).to have_css('li#invoices.selected')
53 | end
54 |
55 | it "renders the item's 'a' tag with the selected classes" do
56 | expect(output).to have_css('li#invoices a.selected')
57 | end
58 | end
59 |
60 | context 'when the :ordered option is true' do
61 | let(:options) {{ level: :all, ordered: true }}
62 |
63 | it "renders an 'ol' tag for the navigation" do
64 | expect(output).to have_css('ol')
65 | end
66 |
67 | it "sets the right html id on the rendered 'ol' tag" do
68 | expect(output).to have_css('ol#nav_id')
69 | end
70 |
71 | it "sets the right html classes on the rendered 'ol' tag" do
72 | expect(output).to have_css('ol.nav_class')
73 | end
74 | end
75 |
76 | context 'when a sub navigation item is selected' do
77 | before do
78 | allow(navigation[:invoices]).to receive_messages(selected?: true)
79 |
80 | allow(navigation[:invoices].sub_navigation[:unpaid]).to \
81 | receive_messages(selected?: true, selected_by_condition?: true)
82 | end
83 |
84 | it 'renders the parent items as selected' do
85 | expect(output).to have_css('li#invoices.selected')
86 | end
87 |
88 | it "renders the selected nested item's link as selected" do
89 | expect(output).to have_css('li#unpaid.selected')
90 | end
91 | end
92 | end
93 | end
94 | end
95 | end
96 |
--------------------------------------------------------------------------------
/spec/simple_navigation/renderer/text_spec.rb:
--------------------------------------------------------------------------------
1 | module SimpleNavigation
2 | module Renderer
3 | describe Text do
4 | let!(:navigation) { setup_navigation('nav_id', 'nav_class') }
5 |
6 | let(:item) { nil }
7 | let(:options) {{ level: :all }}
8 | let(:output) { renderer.render(navigation) }
9 | let(:renderer) { Text.new(options) }
10 |
11 | before { select_an_item(navigation[item]) if item }
12 |
13 | describe '#render' do
14 | context 'when no item is selected' do
15 | it 'renders an empty string' do
16 | expect(output).to eq ''
17 | end
18 | end
19 |
20 | context 'when an item is selected' do
21 | let(:item) { :invoices }
22 |
23 | it "renders the selected item's name" do
24 | expect(output).to eq 'Invoices'
25 | end
26 | end
27 |
28 | context 'when a sub navigation item is selected' do
29 | before do
30 | allow(navigation[:invoices]).to receive_messages(selected?: true)
31 |
32 | allow(navigation[:invoices].sub_navigation[:unpaid]).to \
33 | receive_messages(selected?: true, selected_by_condition?: true)
34 | end
35 |
36 | it 'separates the items with a space' do
37 | expect(output).to eq 'Invoices Unpaid'
38 | end
39 |
40 | context "and the :join_with option is set" do
41 | let(:options) {{ level: :all, join_with: ' | ' }}
42 |
43 | it 'separates the items with the specified separator' do
44 | expect(output).to eq 'Invoices | Unpaid'
45 | end
46 | end
47 | end
48 | end
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/spec/simple_navigation_spec.rb:
--------------------------------------------------------------------------------
1 | describe SimpleNavigation do
2 | before { subject.config_file_path = 'path_to_config' }
3 |
4 | describe 'config_file_path=' do
5 | before { subject.config_file_paths = ['existing_path'] }
6 |
7 | it 'overrides the config_file_paths' do
8 | subject.config_file_path = 'new_path'
9 | expect(subject.config_file_paths).to eq ['new_path']
10 | end
11 | end
12 |
13 | describe '.default_config_file_path' do
14 | before { allow(subject).to receive_messages(root: 'root') }
15 |
16 | it 'returns the config file path according to :root setting' do
17 | expect(subject.default_config_file_path).to eq 'root/config'
18 | end
19 | end
20 |
21 | describe 'Regarding renderers' do
22 | it 'registers the builtin renderers by default' do
23 | expect(subject.registered_renderers).not_to be_empty
24 | end
25 |
26 | describe '.register_renderer' do
27 | let(:renderer) { double(:renderer) }
28 |
29 | it 'adds the specified renderer to the list of renderers' do
30 | subject.register_renderer(my_renderer: renderer)
31 | expect(subject.registered_renderers[:my_renderer]).to be renderer
32 | end
33 | end
34 | end
35 |
36 | describe '.set_env' do
37 | before do
38 | subject.config_file_paths = []
39 | allow(subject).to receive_messages(default_config_file_path: 'default_path')
40 | subject.set_env('root', 'my_env')
41 | end
42 |
43 | it 'sets the root' do
44 | expect(subject.root).to eq 'root'
45 | end
46 |
47 | it 'sets the environment' do
48 | expect(subject.environment).to eq 'my_env'
49 | end
50 |
51 | it 'adds the default-config path to the list of config_file_paths' do
52 | expect(subject.config_file_paths).to eq ['default_path']
53 | end
54 | end
55 |
56 | describe '.load_config', memfs: true do
57 | let(:paths) { ['/path/one', '/path/two'] }
58 |
59 | before do
60 | FileUtils.mkdir_p(paths)
61 | allow(subject).to receive_messages(config_file_paths: paths)
62 | end
63 |
64 | context 'when the config file for the context exists' do
65 | before do
66 | File.open('/path/two/navigation.rb', 'w') { |f| f.puts 'default content' }
67 | File.open('/path/one/other_navigation.rb', 'w') { |f| f.puts 'other content' }
68 | end
69 |
70 | context 'when no context is provided' do
71 | it 'stores the configuration in config_files for the default context' do
72 | subject.load_config
73 | expect(subject.config_files[:default]).to eq "default content\n"
74 | end
75 | end
76 |
77 | context 'when a context is provided' do
78 | it 'stores the configuration in config_files for the given context' do
79 | subject.load_config(:other)
80 | expect(subject.config_files[:other]).to eq "other content\n"
81 | end
82 | end
83 |
84 | context 'and environment is production' do
85 | before { allow(subject).to receive_messages(environment: 'production') }
86 |
87 | it 'loads the config file only for the first call' do
88 | subject.load_config
89 | File.open('/path/two/navigation.rb', 'w') { |f| f.puts 'new content' }
90 | subject.load_config
91 | expect(subject.config_files[:default]).to eq "default content\n"
92 | end
93 | end
94 |
95 | context "and environment isn't production" do
96 | it 'loads the config file for every call' do
97 | subject.load_config
98 | File.open('/path/two/navigation.rb', 'w') { |f| f.puts 'new content' }
99 | subject.load_config
100 | expect(subject.config_files[:default]).to eq "new content\n"
101 | end
102 | end
103 | end
104 |
105 | context "when the config file for the context doesn't exists" do
106 | it 'raises an exception' do
107 | expect{ subject.load_config }.to raise_error(RuntimeError, /Config file 'navigation.rb' not found in path\(s\)/)
108 | end
109 | end
110 | end
111 |
112 | describe '.config' do
113 | it 'returns the Configuration singleton instance' do
114 | expect(subject.config).to be SimpleNavigation::Configuration.instance
115 | end
116 | end
117 |
118 | describe '.active_item_container_for' do
119 | let(:primary) { double(:primary) }
120 |
121 | before { allow(subject.config).to receive_messages(primary_navigation: primary) }
122 |
123 | context 'when level is :all' do
124 | it 'returns the primary_navigation' do
125 | nav = subject.active_item_container_for(:all)
126 | expect(nav).to be primary
127 | end
128 | end
129 |
130 | context 'when level is :leaves' do
131 | it 'returns the currently active leaf-container' do
132 | expect(primary).to receive(:active_leaf_container)
133 | subject.active_item_container_for(:leaves)
134 | end
135 | end
136 |
137 | context 'when level is a Range' do
138 | it 'takes the min of the range to lookup the active container' do
139 | expect(primary).to receive(:active_item_container_for).with(2)
140 | subject.active_item_container_for(2..3)
141 | end
142 | end
143 |
144 | context 'when level is an Integer' do
145 | it 'considers the Integer to lookup the active container' do
146 | expect(primary).to receive(:active_item_container_for).with(1)
147 | subject.active_item_container_for(1)
148 | end
149 | end
150 |
151 | context 'when level is something else' do
152 | it 'raises an exception' do
153 | expect{
154 | subject.active_item_container_for('something else')
155 | }.to raise_error(ArgumentError, 'Invalid navigation level: something else')
156 | end
157 | end
158 | end
159 |
160 | describe '.load_adapter' do
161 | shared_examples 'loading the right adapter' do |framework, adapter|
162 | context "when the context is #{framework}" do
163 | before do
164 | allow(subject).to receive_messages(framework: framework)
165 | subject.load_adapter
166 | end
167 |
168 | it "returns the #{framework} adapter" do
169 | adapter_class = SimpleNavigation::Adapters.const_get(adapter)
170 | expect(subject.adapter_class).to be adapter_class
171 | end
172 | end
173 | end
174 |
175 | it_behaves_like 'loading the right adapter', :rails, :Rails
176 | it_behaves_like 'loading the right adapter', :padrino, :Padrino
177 | it_behaves_like 'loading the right adapter', :sinatra, :Sinatra
178 | end
179 |
180 | describe '.init_adapter_from' do
181 | let(:adapter) { double(:adapter) }
182 | let(:adapter_class) { double(:adapter_class, new: adapter) }
183 |
184 | it 'sets the adapter to a new instance of adapter_class' do
185 | subject.adapter_class = adapter_class
186 | subject.init_adapter_from(:default)
187 | expect(subject.adapter).to be adapter
188 | end
189 | end
190 | end
191 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'initializers/have_css_matcher'
2 | require 'initializers/memfs'
3 | require 'initializers/coveralls'
4 | require 'initializers/rails'
5 | require 'initializers/rspec'
6 | require 'capybara/rspec'
7 |
8 | require 'bundler/setup'
9 | Bundler.require
10 |
11 | if defined? Rails
12 | require 'fake_app/rails_app'
13 | require 'rspec/rails'
14 |
15 | Capybara.app = RailsApp::Application
16 |
17 | RSpec.configure do |config|
18 | config.before do
19 | SimpleNavigation.config_files.clear
20 | setup_adapter_for :rails
21 | end
22 | end
23 | end
24 |
25 | def setup_adapter_for(framework, context = double(:context))
26 | if framework == :rails
27 | # Rails 6.0 and 6.1 provide ActionView::Base.empty method that creates ActionView with an empty LookupContext.
28 | # The method is not available on older versions
29 | view_context = ActionView::Base.respond_to?(:empty) ? ActionView::Base.empty : ActionView::Base.new
30 | allow(context).to receive_messages(view_context: view_context)
31 | end
32 |
33 | allow(SimpleNavigation).to receive_messages(framework: framework)
34 | SimpleNavigation.load_adapter
35 | SimpleNavigation.init_adapter_from(context)
36 | end
37 |
38 | def select_an_item(item)
39 | allow(item).to receive_messages(selected?: true)
40 | end
41 |
42 | def setup_container(dom_id, dom_class)
43 | container = SimpleNavigation::ItemContainer.new(1)
44 | container.dom_id = dom_id
45 | container.dom_class = dom_class
46 | container
47 | end
48 |
49 | def setup_navigation(dom_id, dom_class)
50 | setup_adapter_for :rails
51 | container = setup_container(dom_id, dom_class)
52 | setup_items(container)
53 | container
54 | end
55 |
56 | # FIXME: adding the :link option for the list renderer messes up the other
57 | # renderers
58 | def setup_items(container)
59 | container.item :users, 'Users', '/users', html: { id: 'users_id' }, link_html: { id: 'users_link_id' }
60 | container.item :invoices, 'Invoices', '/invoices' do |invoices|
61 | invoices.item :paid, 'Paid', '/invoices/paid'
62 | invoices.item :unpaid, 'Unpaid', '/invoices/unpaid'
63 | end
64 | container.item :accounts, 'Accounts', '/accounts', html: { style: 'float:right' }
65 | container.item :miscellany, 'Miscellany'
66 |
67 | container.items.each do |item|
68 | allow(item).to receive_messages(selected?: false, selected_by_condition?: false)
69 |
70 | if item.sub_navigation
71 | item.sub_navigation.items.each do |item|
72 | allow(item).to receive_messages(selected?: false, selected_by_condition?: false)
73 | end
74 | end
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/uninstall.rb:
--------------------------------------------------------------------------------
1 | # Uninstall hook code here
2 |
--------------------------------------------------------------------------------