├── .github
└── workflows
│ ├── dynamic-readme.yml
│ └── dynamic-security.yml
├── .gitignore
├── .ruby-version
├── .travis.yml
├── CONTRIBUTING.md
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── Rakefile
├── SECURITY.md
├── app
└── views
│ └── breadcrumbs
│ └── _breadcrumbs.html.erb
├── croutons.gemspec
├── lib
├── croutons.rb
└── croutons
│ ├── breadcrumb.rb
│ ├── breadcrumb_trail.rb
│ ├── controller.rb
│ ├── engine.rb
│ └── version.rb
└── spec
├── features
└── breadcrumbs_spec.rb
├── lib
└── croutons
│ ├── breadcrumb_spec.rb
│ └── breadcrumb_trail_spec.rb
├── spec_helper.rb
└── support
└── rails_app.rb
/.github/workflows/dynamic-readme.yml:
--------------------------------------------------------------------------------
1 | name: update-templates
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | workflow_dispatch:
8 |
9 | jobs:
10 | update-templates:
11 | permissions:
12 | contents: write
13 | pull-requests: write
14 | pages: write
15 | uses: thoughtbot/templates/.github/workflows/dynamic-readme.yaml@main
16 | secrets:
17 | token: ${{ secrets.GITHUB_TOKEN }}
18 |
--------------------------------------------------------------------------------
/.github/workflows/dynamic-security.yml:
--------------------------------------------------------------------------------
1 | name: update-security
2 |
3 | on:
4 | push:
5 | paths:
6 | - SECURITY.md
7 | branches:
8 | - main
9 | workflow_dispatch:
10 |
11 | jobs:
12 | update-security:
13 | permissions:
14 | contents: write
15 | pull-requests: write
16 | pages: write
17 | uses: thoughtbot/templates/.github/workflows/dynamic-security.yaml@main
18 | secrets:
19 | token: ${{ secrets.GITHUB_TOKEN }}
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | pkg/
3 | spec/dummy_app
4 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.5.3
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | cache: bundler
3 | rvm:
4 | - 2.3.8
5 | - 2.4.5
6 | - 2.5.3
7 | script: bundle exec rspec
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | We love pull requests from everyone. By participating in this project, you agree
2 | to abide by the thoughtbot [code of conduct].
3 |
4 | [code of conduct]: https://thoughtbot.com/open-source-code-of-conduct
5 |
6 | ## Contributing
7 |
8 | 1. Fork the repo.
9 |
10 | 2. Run the tests. We only take pull requests with passing tests, and it's great
11 | to know that you have a clean slate: `bundle exec rake`
12 |
13 | 3. Add a test for your change. Only refactoring and documentation changes
14 | require no new tests. If you are adding functionality or fixing a bug, we
15 | need a test!
16 |
17 | 4. Make the test pass.
18 |
19 | 5. Push to your fork and submit a pull request.
20 |
21 | At this point you're waiting on us. We like to at least comment on, if not
22 | accept, pull requests within three business days (and, typically, one business
23 | day). We may suggest some changes or improvements or alternatives.
24 |
25 | Some things that will increase the chance that your pull request is accepted,
26 | taken straight from the Ruby on Rails guide:
27 |
28 | * Use Rails idioms and helpers
29 | * Include tests that fail without your code, and pass with it
30 | * Update the documentation, the surrounding one, examples elsewhere, guides,
31 | whatever is affected by your contribution
32 |
33 | Syntax:
34 |
35 | * Two spaces, no tabs.
36 | * No trailing whitespace. Blank lines should not have any space.
37 | * Prefer &&/|| over and/or.
38 | * MyClass.my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
39 | * a = b and not a=b.
40 | * Follow the conventions you see used in the source already.
41 |
42 | And in case we didn't emphasize it enough: we love tests!
43 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | # Declare your gem's dependencies in croutons.gemspec.
4 | # Bundler will treat runtime dependencies like base dependencies, and
5 | # development dependencies will be added by default to the :development group.
6 | gemspec
7 |
8 | # Declare any dependencies that are still in development here instead of in
9 | # your gemspec. These might include edge Rails or gems from your path or
10 | # Git. Remember to move these dependencies to your gemspec before releasing
11 | # your gem to rubygems.org.
12 |
13 | # To use debugger
14 | # gem 'debugger'
15 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | croutons (0.0.2)
5 | rails (>= 4.2.0)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (5.2.2)
11 | actionpack (= 5.2.2)
12 | nio4r (~> 2.0)
13 | websocket-driver (>= 0.6.1)
14 | actionmailer (5.2.2)
15 | actionpack (= 5.2.2)
16 | actionview (= 5.2.2)
17 | activejob (= 5.2.2)
18 | mail (~> 2.5, >= 2.5.4)
19 | rails-dom-testing (~> 2.0)
20 | actionpack (5.2.2)
21 | actionview (= 5.2.2)
22 | activesupport (= 5.2.2)
23 | rack (~> 2.0)
24 | rack-test (>= 0.6.3)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
27 | actionview (5.2.2)
28 | activesupport (= 5.2.2)
29 | builder (~> 3.1)
30 | erubi (~> 1.4)
31 | rails-dom-testing (~> 2.0)
32 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
33 | activejob (5.2.2)
34 | activesupport (= 5.2.2)
35 | globalid (>= 0.3.6)
36 | activemodel (5.2.2)
37 | activesupport (= 5.2.2)
38 | activerecord (5.2.2)
39 | activemodel (= 5.2.2)
40 | activesupport (= 5.2.2)
41 | arel (>= 9.0)
42 | activestorage (5.2.2)
43 | actionpack (= 5.2.2)
44 | activerecord (= 5.2.2)
45 | marcel (~> 0.3.1)
46 | activesupport (5.2.2)
47 | concurrent-ruby (~> 1.0, >= 1.0.2)
48 | i18n (>= 0.7, < 2)
49 | minitest (~> 5.1)
50 | tzinfo (~> 1.1)
51 | addressable (2.5.2)
52 | public_suffix (>= 2.0.2, < 4.0)
53 | arel (9.0.0)
54 | builder (3.2.3)
55 | capybara (3.12.0)
56 | addressable
57 | mini_mime (>= 0.1.3)
58 | nokogiri (~> 1.8)
59 | rack (>= 1.6.0)
60 | rack-test (>= 0.6.3)
61 | regexp_parser (~> 1.2)
62 | xpath (~> 3.2)
63 | concurrent-ruby (1.1.4)
64 | crass (1.0.5)
65 | diff-lcs (1.3)
66 | erubi (1.8.0)
67 | globalid (0.4.1)
68 | activesupport (>= 4.2.0)
69 | i18n (1.4.0)
70 | concurrent-ruby (~> 1.0)
71 | loofah (2.4.0)
72 | crass (~> 1.0.2)
73 | nokogiri (>= 1.5.9)
74 | mail (2.7.1)
75 | mini_mime (>= 0.1.1)
76 | marcel (0.3.3)
77 | mimemagic (~> 0.3.2)
78 | method_source (0.9.2)
79 | mimemagic (0.3.3)
80 | mini_mime (1.0.1)
81 | mini_portile2 (2.4.0)
82 | minitest (5.11.3)
83 | nio4r (2.3.1)
84 | nokogiri (1.10.8)
85 | mini_portile2 (~> 2.4.0)
86 | public_suffix (3.0.3)
87 | rack (2.0.8)
88 | rack-test (1.1.0)
89 | rack (>= 1.0, < 3)
90 | rails (5.2.2)
91 | actioncable (= 5.2.2)
92 | actionmailer (= 5.2.2)
93 | actionpack (= 5.2.2)
94 | actionview (= 5.2.2)
95 | activejob (= 5.2.2)
96 | activemodel (= 5.2.2)
97 | activerecord (= 5.2.2)
98 | activestorage (= 5.2.2)
99 | activesupport (= 5.2.2)
100 | bundler (>= 1.3.0)
101 | railties (= 5.2.2)
102 | sprockets-rails (>= 2.0.0)
103 | rails-dom-testing (2.0.3)
104 | activesupport (>= 4.2.0)
105 | nokogiri (>= 1.6)
106 | rails-html-sanitizer (1.0.4)
107 | loofah (~> 2.2, >= 2.2.2)
108 | railties (5.2.2)
109 | actionpack (= 5.2.2)
110 | activesupport (= 5.2.2)
111 | method_source
112 | rake (>= 0.8.7)
113 | thor (>= 0.19.0, < 2.0)
114 | rake (12.3.2)
115 | regexp_parser (1.3.0)
116 | rspec-core (3.8.0)
117 | rspec-support (~> 3.8.0)
118 | rspec-expectations (3.8.2)
119 | diff-lcs (>= 1.2.0, < 2.0)
120 | rspec-support (~> 3.8.0)
121 | rspec-mocks (3.8.0)
122 | diff-lcs (>= 1.2.0, < 2.0)
123 | rspec-support (~> 3.8.0)
124 | rspec-rails (3.8.1)
125 | actionpack (>= 3.0)
126 | activesupport (>= 3.0)
127 | railties (>= 3.0)
128 | rspec-core (~> 3.8.0)
129 | rspec-expectations (~> 3.8.0)
130 | rspec-mocks (~> 3.8.0)
131 | rspec-support (~> 3.8.0)
132 | rspec-support (3.8.0)
133 | sprockets (3.7.2)
134 | concurrent-ruby (~> 1.0)
135 | rack (> 1, < 3)
136 | sprockets-rails (3.2.1)
137 | actionpack (>= 4.0)
138 | activesupport (>= 4.0)
139 | sprockets (>= 3.0.0)
140 | sqlite3 (1.3.13)
141 | thor (0.20.3)
142 | thread_safe (0.3.6)
143 | tzinfo (1.2.5)
144 | thread_safe (~> 0.1)
145 | websocket-driver (0.7.0)
146 | websocket-extensions (>= 0.1.0)
147 | websocket-extensions (0.1.3)
148 | xpath (3.2.0)
149 | nokogiri (~> 1.8)
150 |
151 | PLATFORMS
152 | ruby
153 |
154 | DEPENDENCIES
155 | capybara
156 | croutons!
157 | rspec-rails
158 | sqlite3
159 |
160 | BUNDLED WITH
161 | 1.17.2
162 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014-2017 Calle Erlandsson, George Brocklehurst, and thoughtbot inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Deprecated as of September 20, 2024
2 |
3 | This project is no longer maintained. If you wish to continue to develop this code yourself, we recommend you fork it.
4 |
5 | Croutons
6 | ========
7 |
8 | Easy breadcrumbs for Rails apps.
9 |
10 | Usage
11 | -----
12 |
13 | ### Required steps
14 |
15 | 1. Include `Croutons::Controller` in your `ApplicationController`.
16 |
17 | This will make a `#breadcrumbs` helper available in your layouts and views.
18 | 2. Call the `#breadcrumbs` helper in your layouts or views.
19 | 3. Define a `BreadcrumbTrail` class, which inherits from
20 | `Croutons::BreadcrumbTrail`.
21 | 4. Define missing methods on the `BreadcrumbTrail` class.
22 |
23 | For example, for the `admin/locations/index.html.erb` view you would define
24 | an `#admin_locations_index` method.
25 |
26 | In these methods, you build up a breadcrumb trail by calling `#breadcrumb`
27 | with a label and an optional URL. You can also call previously defined
28 | methods to build on existing trails. View assigns (i.e. the controller
29 | instance variables) are available via the `#objects` method which returns a
30 | `Hash`. Rails route helpers are also available inside this class.
31 |
32 | Please see [the example below](#example) for further reference.
33 |
34 | ### Optional steps
35 |
36 | * Instead of defining a `BreadcrumbTrail` class you can use an object of your
37 | own that responds to `#breadcrumbs`.
38 |
39 | To do this, override the private `#breadcrumb_trail` method in the controller
40 | where you included `Croutons::Controller`, to return the object you want to
41 | use.
42 |
43 | The `#breadcrumbs` method is passed two parameters: one `template_identifier`
44 | `String` and one `objects` `Hash`. The `#breadcrumbs` method should return an
45 | `Array` of `Croutons::Breadcrumb`s.
46 |
47 | * Override the view used to render breadcrumbs.
48 |
49 | To do this, create a view called `breadcrumbs/_breadcrumbs.html.erb`.
50 |
51 | In this view, an `Array` of `Croutons::Breadcrumb`s is assigned to the local
52 | variable `breadcrumbs`. These `Croutons::Breadcrumb`s have two public
53 | attributes: `#label` and `#url`. The `#url` attribute is optional. To check
54 | whether the `Croutons::Breadcrumb` has a `#url` or not (i.e. should be
55 | rendered as a link or not), check whether the `#link?` method returns `true`
56 | or `false`.
57 |
58 | ### Example
59 |
60 | #### `app/controllers/application_controller.rb`
61 |
62 | class ApplicationController < ActionController::Base
63 | include Croutons::Controller
64 | end
65 |
66 | #### `app/controllers/posts_controller.rb`
67 |
68 | class PostsController < ApplicationController
69 | def index
70 | @posts = Post.all
71 | end
72 |
73 | def show
74 | @post = Post.find(params[:id])
75 | end
76 | end
77 |
78 | #### `app/views/layouts/application.html.erb`
79 |
80 |
81 |
82 |
83 | My blog
84 |
85 |
86 | <%= breadcrumbs %>
87 | <%= yield %>
88 |
89 |
90 |
91 | #### `app/models/breadcrumb_trail.rb`
92 |
93 | class BreadcrumbTrail < Croutons::BreadcrumbTrail
94 | def posts_index
95 | breadcrumb("Posts", posts_path)
96 | end
97 |
98 | def posts_show
99 | posts_index
100 | breadcrumb(objects[:post].title, post_path(objects[:post]))
101 | end
102 | end
103 |
104 | License
105 | -------
106 |
107 | Croutons is Copyright © 2014 Calle Erlandsson, George Brocklehurst, and
108 | thoughtbot. It is free software, and may be redistributed under the terms
109 | specified in the LICENSE file.
110 |
111 |
112 | ## About thoughtbot
113 |
114 | 
115 |
116 | This repo is maintained and funded by thoughtbot, inc.
117 | The names and logos for thoughtbot are trademarks of thoughtbot, inc.
118 |
119 | We love open source software!
120 | See [our other projects][community].
121 | We are [available for hire][hire].
122 |
123 | [community]: https://thoughtbot.com/community?utm_source=github
124 | [hire]: https://thoughtbot.com/hire-us?utm_source=github
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | begin
2 | require 'bundler/setup'
3 | rescue LoadError
4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5 | end
6 |
7 | Bundler::GemHelper.install_tasks
8 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/views/breadcrumbs/_breadcrumbs.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <% breadcrumbs.each do |breadcrumb| %>
3 | <% if breadcrumb.link? %>
4 | - <%= link_to breadcrumb.label, breadcrumb.url %>
5 | <% else %>
6 | - <%= breadcrumb.label %>
7 | <% end %>
8 | <% end %>
9 |
10 |
--------------------------------------------------------------------------------
/croutons.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 |
3 | require "croutons/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "croutons"
7 | s.version = Croutons::VERSION
8 | s.authors = ["Calle Erlandsson", "George Brocklehurst"]
9 | s.email = ["calle@thoughtbot.com", "george@thoughtbot.com", "hello@thoughtbot.com"]
10 | s.homepage = "https://github.com/thoughtbot/croutons"
11 | s.summary = "Easy breadcrumbs for Rails apps."
12 | s.license = "MIT"
13 | s.files = `git ls-files -z`.split("\x0")
14 | s.test_files = s.files.grep(%r{^(test|spec|features)/})
15 | s.require_paths = ["lib"]
16 | s.add_dependency "rails", ">= 4.2.0"
17 | s.add_development_dependency "rspec-rails"
18 | s.add_development_dependency "capybara"
19 | s.add_development_dependency "sqlite3"
20 | end
21 |
--------------------------------------------------------------------------------
/lib/croutons.rb:
--------------------------------------------------------------------------------
1 | require 'croutons/engine'
2 | require 'croutons/controller'
3 | require 'croutons/breadcrumb_trail'
4 |
5 | module Croutons
6 | end
7 |
--------------------------------------------------------------------------------
/lib/croutons/breadcrumb.rb:
--------------------------------------------------------------------------------
1 | module Croutons
2 | class Breadcrumb
3 | attr_reader :label, :url
4 |
5 | def initialize(label, url = nil)
6 | self.label = label
7 | self.url = url
8 | end
9 |
10 | def labelize
11 | self.url = nil
12 | end
13 |
14 | def link?
15 | url.present?
16 | end
17 |
18 | def ==(other)
19 | label == other.label && url == other.url
20 | end
21 |
22 | private
23 |
24 | attr_writer :label, :url
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/croutons/breadcrumb_trail.rb:
--------------------------------------------------------------------------------
1 | require 'croutons/breadcrumb'
2 |
3 | module Croutons
4 | class BreadcrumbTrail
5 | def self.breadcrumbs(*args)
6 | new(*args).breadcrumbs
7 | end
8 |
9 | attr_reader :breadcrumbs
10 |
11 | def initialize(template_identifer, objects = {})
12 | @template_identifer = template_identifer
13 | @objects = objects.with_indifferent_access
14 | @breadcrumbs = []
15 | build_breadcrumbs
16 | end
17 |
18 | def method_missing(name, *args)
19 | if respond_to_missing?(name)
20 | Rails.application.routes.url_helpers.public_send(name, *args)
21 | else
22 | super
23 | end
24 | end
25 |
26 | def respond_to_missing?(name)
27 | Rails.application.routes.url_helpers.respond_to?(name)
28 | end
29 |
30 | private
31 |
32 | attr_reader :template_identifer, :objects
33 |
34 | def build_breadcrumbs
35 | send(template_identifer)
36 | labelize_last
37 | end
38 |
39 | def labelize_last
40 | breadcrumbs.last.try(:labelize)
41 | end
42 |
43 | def breadcrumb(*args)
44 | breadcrumbs << Breadcrumb.new(*args)
45 | end
46 |
47 | def t(*args)
48 | I18n.t(*args)
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/croutons/controller.rb:
--------------------------------------------------------------------------------
1 | module Croutons
2 | module Controller
3 | def self.included(controller)
4 | controller.helper_method(:breadcrumbs)
5 | end
6 |
7 | def render_to_body(options)
8 | @_template = options[:template]
9 | @_prefixes = options[:prefixes]
10 | super
11 | end
12 |
13 | private
14 |
15 | def breadcrumbs(objects = {})
16 | template = lookup_context.find_template(@_template, @_prefixes)
17 | template_identifier = template.virtual_path.gsub('/', '_')
18 | objects.reverse_merge!(view_assigns)
19 | breadcrumbs = breadcrumb_trail.breadcrumbs(template_identifier, objects)
20 | render_to_string(
21 | partial: 'breadcrumbs/breadcrumbs',
22 | locals: { breadcrumbs: breadcrumbs },
23 | )
24 | end
25 |
26 | def breadcrumb_trail
27 | ::BreadcrumbTrail
28 | rescue NameError
29 | raise NotImplementedError,
30 | 'Define a `BreadcrumbTrail` class that inherits from '\
31 | '`Breadcrumbs::BreadcrumbTrail`, or override the '\
32 | '`breadcrumb_trail` method in your controller so that it '\
33 | 'returns an object that responds to `#breadcrumbs`.'
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/croutons/engine.rb:
--------------------------------------------------------------------------------
1 | require 'rails'
2 |
3 | module Croutons
4 | class Engine < Rails::Engine
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/lib/croutons/version.rb:
--------------------------------------------------------------------------------
1 | module Croutons
2 | VERSION = "0.0.2"
3 | end
4 |
--------------------------------------------------------------------------------
/spec/features/breadcrumbs_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe "Breadcrumbs" do
4 | before(:all) do |test|
5 | setup_rails_app do |rails_app|
6 | rails_app.scaffold_model "Post", "title:string"
7 | rails_app.add_croutons_mixin_to_application_controller
8 | rails_app.add_to_view('posts/index', '<%= breadcrumbs %>')
9 | rails_app.add_to_view('posts/show', '<%= breadcrumbs role: params[:role] %>')
10 | rails_app.add_to_view('posts/new', '<%= breadcrumbs %>')
11 | rails_app.add_breadcrumb_trail_class <<-RUBY
12 | require "croutons/breadcrumb_trail"
13 |
14 | class BreadcrumbTrail < Croutons::BreadcrumbTrail
15 | private
16 |
17 | def posts_index
18 | breadcrumb("Posts", posts_path)
19 | end
20 |
21 | def posts_show
22 | if objects[:role] == "admin"
23 | posts_index
24 | end
25 | breadcrumb(objects[:post].title)
26 | end
27 | end
28 | RUBY
29 | end
30 |
31 | test.class.send :include, Capybara::DSL
32 | test.class.send :include, Rails.application.routes.url_helpers
33 | end
34 |
35 | it "are rendered on page" do
36 | post = Post.create!(title: "My first post")
37 |
38 | visit posts_path
39 |
40 | with_breadcrumbs do |items|
41 | expect(items.length).to eq 1
42 | expect(items.first).to have_content("Posts")
43 | expect(items.first).not_to have_css("a")
44 | end
45 |
46 | visit post_path(post, role: "admin")
47 |
48 | with_breadcrumbs do |items|
49 | expect(items.length).to eq 2
50 | expect(items.first).to have_link("Posts", href: posts_path)
51 | expect(items.last).to have_content(post.title)
52 | expect(items.last).not_to have_css("a")
53 | end
54 |
55 | visit post_path(post, role: "guest")
56 |
57 | with_breadcrumbs do |items|
58 | expect(items.length).to eq 1
59 | expect(items.first).to have_content(post.title)
60 | expect(items.first).not_to have_css("a")
61 | end
62 | end
63 |
64 | context "when not defined" do
65 | it "raises a helpful exception" do
66 | expect { visit new_post_path }.to raise_exception(/posts_new/)
67 | end
68 | end
69 |
70 | def with_breadcrumbs
71 | within(".breadcrumbs") do
72 | yield all("li")
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/spec/lib/croutons/breadcrumb_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "croutons/breadcrumb"
3 |
4 | describe Croutons::Breadcrumb do
5 | describe "#labelize" do
6 | it "sets url to nil" do
7 | breadcrumb = described_class.new("Some text", "Some url")
8 |
9 | breadcrumb.labelize
10 |
11 | expect(breadcrumb.url).to be_nil
12 | end
13 | end
14 |
15 | describe "#link?" do
16 | it "returns true when the url is present" do
17 | expect(described_class.new("Some text", "Some url")).to be_link
18 | end
19 |
20 | it "returns false when the url is blank" do
21 | expect(described_class.new("Some text", nil)).not_to be_link
22 | end
23 | end
24 |
25 | describe "#==" do
26 | it "returns true when the label and URL match" do
27 | one = described_class.new("label", "url")
28 | two = described_class.new("label", "url")
29 |
30 | expect(one).to eq(two)
31 | end
32 |
33 | it "returns false when the labels differ" do
34 | one = described_class.new("label", "url")
35 | two = described_class.new("other", "url")
36 |
37 | expect(one).not_to eq(two)
38 | end
39 |
40 | it "returns false when the URLs differ" do
41 | one = described_class.new("label", "url")
42 | two = described_class.new("label", "other")
43 |
44 | expect(one).not_to eq(two)
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/spec/lib/croutons/breadcrumb_trail_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 | require "croutons/breadcrumb_trail"
3 | require "croutons/breadcrumb"
4 | require "rails"
5 |
6 | describe Croutons::BreadcrumbTrail do
7 | describe ".breadcrumbs" do
8 | let(:trail) do
9 | Class.new(described_class) do
10 | def one
11 | breadcrumb("One", "/one")
12 | end
13 |
14 | def two
15 | one
16 | breadcrumb("Two", "/two")
17 | end
18 |
19 | def thing
20 | breadcrumb(objects[:thing])
21 | end
22 |
23 | def translated
24 | breadcrumb(t(objects[:key]))
25 | end
26 |
27 | def routes
28 | breadcrumb("Some path", some_path)
29 | breadcrumb("You are here")
30 | end
31 | end
32 | end
33 |
34 | context "for a known template identifier" do
35 | it "returns breadcrumbs" do
36 | expect(trail.breadcrumbs(:one)).to eq [
37 | Croutons::Breadcrumb.new("One"),
38 | ]
39 | end
40 |
41 | it "removes the URL from the final crumb" do
42 | expect(trail.breadcrumbs(:two)).to eq [
43 | Croutons::Breadcrumb.new("One", "/one"),
44 | Croutons::Breadcrumb.new("Two"),
45 | ]
46 | end
47 |
48 | it "makes the given objects available through #objects" do
49 | expect(trail.breadcrumbs(:thing, thing: "Foo")).
50 | to eq [Croutons::Breadcrumb.new("Foo")]
51 | expect(trail.breadcrumbs(:thing, "thing" => "Bar")).
52 | to eq [Croutons::Breadcrumb.new("Bar")]
53 | end
54 |
55 | it "makes I18n available" do
56 | allow(I18n).to receive(:t).
57 | with("some.translation").
58 | and_return("translated string")
59 | expect(trail.breadcrumbs(:translated, key: "some.translation")).
60 | to eq [Croutons::Breadcrumb.new("translated string")]
61 | end
62 |
63 | it "makes Rails route helpers available" do
64 | route_helpers = double("route_helpers", some_path: "/some/path")
65 | allow(Rails).
66 | to receive_message_chain(:application, :routes, :url_helpers).
67 | and_return(route_helpers)
68 |
69 | expect(trail.breadcrumbs(:routes)).to eq [
70 | Croutons::Breadcrumb.new("Some path", "/some/path"),
71 | Croutons::Breadcrumb.new("You are here"),
72 | ]
73 | end
74 | end
75 |
76 | context "for an unknown template identifier" do
77 | it "raises a helpful exception" do
78 | expect { trail.breadcrumbs(:not_real) }.
79 | to raise_exception(NoMethodError)
80 | end
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "active_support/all"
2 |
3 | ENV["RAILS_ENV"] = "test"
4 |
5 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
6 |
7 | RSpec.configure do |config|
8 | config.expect_with :rspec do |c|
9 | c.syntax = :expect
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/support/rails_app.rb:
--------------------------------------------------------------------------------
1 | class RailsApp
2 | PROJECT_ROOT = File.expand_path("../../..", __FILE__).freeze
3 |
4 | module Helpers
5 | def setup_rails_app(&block)
6 | RailsApp.new.setup(&block)
7 | end
8 | end
9 |
10 | def setup
11 | create_rails_app
12 | disable_class_caching
13 | customize_gemfile
14 | bundle
15 | yield self
16 | setup_database
17 | load_environment
18 | end
19 |
20 | def scaffold_model(name, *columns)
21 | in_app_directory do
22 | run "rails generate scaffold #{name} #{columns.join(' ')} --force"
23 | end
24 | end
25 |
26 | def add_croutons_mixin_to_application_controller
27 | transform_file(path("app/controllers/application_controller.rb")) do |content|
28 | content.sub(
29 | /^(class.*)$/,
30 | "require 'croutons/controller'\n\n\\1\n include Croutons::Controller\n"
31 | )
32 | end
33 | end
34 |
35 | def add_to_view(name, content_to_add)
36 | transform_file(path("app/views/#{name}.html.erb")) do |content|
37 | content << content_to_add
38 | end
39 | end
40 |
41 | def add_breadcrumb_trail_class(source)
42 | File.write(path("app/models/breadcrumb_trail.rb"), source)
43 | end
44 |
45 | private
46 |
47 | def create_rails_app
48 | run "bundle exec rails new #{path} --skip-gemfile --skip-bundle "\
49 | "--skip-git --skip-keeps --skip-spring --skip-javascript "\
50 | "--skip-test-unit --no-rc --skip-sprockets --skip-bootsnap --force"
51 | end
52 |
53 | def disable_class_caching
54 | transform_file(path("config/environments/test.rb")) do |content|
55 | content.gsub(/^\s*config\.cache_classes.*$/, "config.cache_classes = false")
56 | end
57 | end
58 |
59 | def customize_gemfile
60 | File.open(path("Gemfile"), "w") do |f|
61 | f << "source 'https://rubygems.org'\n"
62 | f << "gem 'croutons', path: '#{PROJECT_ROOT}'\n"
63 | f << "gem 'rspec-rails', group: :test\n"
64 | f << "gem 'capybara', group: :test\n"
65 | end
66 | end
67 |
68 | def bundle
69 | in_app_directory do
70 | run "bundle"
71 | end
72 | end
73 |
74 | def setup_database
75 | in_app_directory do
76 | run "rake db:drop:all db:create:all db:migrate"
77 | end
78 | end
79 |
80 | def load_environment
81 | require path("config/environment.rb")
82 | require "rspec/rails"
83 | end
84 |
85 | def in_app_directory(&block)
86 | Dir.chdir(path, &block)
87 | end
88 |
89 | def run(command)
90 | `#{command}`
91 | end
92 |
93 | def transform_file(filename)
94 | content = File.read(filename)
95 | File.open(filename, "w") do |f|
96 | content = yield(content)
97 | f.write(content)
98 | end
99 | end
100 |
101 | def path(filename = "")
102 | File.join(PROJECT_ROOT, "spec", "dummy_app", filename)
103 | end
104 | end
105 |
106 | RSpec.configure do |config|
107 | config.include RailsApp::Helpers
108 | end
109 |
--------------------------------------------------------------------------------