├── .gitignore
├── .rspec
├── .rubocop.yml
├── .rubocop_todo.yml
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── foundation_rails_helper.gemspec
├── lib
├── foundation_rails_helper.rb
├── foundation_rails_helper
│ ├── action_view_extension.rb
│ ├── configuration.rb
│ ├── flash_helper.rb
│ ├── form_builder.rb
│ ├── size_class_calculator.rb
│ └── version.rb
└── railtie.rb
└── spec
├── .rubocop.yml
├── foundation_rails_helper
├── configuration_spec.rb
├── flash_helper_spec.rb
└── form_builder_spec.rb
├── spec_helper.rb
└── support
├── .rubocop.yml
├── classes
├── author.rb
├── book.rb
└── genre.rb
└── mock_rails.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 | .rbenv-gemsets
19 | vendor/bundle/*
20 | .ruby-version
21 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --backtrace
3 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from: .rubocop_todo.yml
2 |
3 | AllCops:
4 | TargetRubyVersion: 2.3
5 | Include:
6 | - Rakefile
7 | - Gemfile
8 | - '*.gemspec'
9 | Exclude:
10 | - 'vendor/**/*'
11 |
12 | Documentation:
13 | Enabled: false
14 |
15 | Style/VariableNumber:
16 | EnforcedStyle: 'snake_case'
17 |
--------------------------------------------------------------------------------
/.rubocop_todo.yml:
--------------------------------------------------------------------------------
1 | # This configuration was generated by
2 | # `rubocop --auto-gen-config`
3 | # on 2017-01-13 13:27:28 -0800 using RuboCop version 0.44.1.
4 | # The point is for the user to remove these configuration records
5 | # one by one as the offenses are removed from the code base.
6 | # Note that changes in the inspected code, or installation of new
7 | # versions of RuboCop, may require this file to be generated again.
8 |
9 | # Offense count: 1
10 | Metrics/AbcSize:
11 | Max: 17
12 |
13 | # Offense count: 1
14 | # Configuration parameters: CountComments.
15 | Metrics/ClassLength:
16 | Max: 183
17 |
18 | # Offense count: 1
19 | Metrics/CyclomaticComplexity:
20 | Max: 7
21 |
22 | # Offense count: 1
23 | # Configuration parameters: CountComments.
24 | Metrics/MethodLength:
25 | Max: 13
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 |
3 | cache: bundler
4 |
5 | branches:
6 | only:
7 | - master
8 | rvm:
9 | - 2.3.8
10 | - 2.4.10
11 | - 2.5.8
12 | - 2.6.6
13 | - 2.7.2
14 | env:
15 | - "RAILS_VERSION=4.2.0"
16 | - "RAILS_VERSION=5.0.0"
17 | - "RAILS_VERSION=5.2.0"
18 | - "RAILS_VERSION=6.0.0"
19 | - "RAILS_VERSION=6.1.0"
20 | jobs:
21 | exclude:
22 | - rvm: 2.4.10
23 | env: "RAILS_VERSION=6.1.0"
24 | - rvm: 2.3.8
25 | env: "RAILS_VERSION=6.1.0"
26 | - rvm: 2.7.2
27 | env: "RAILS_VERSION=6.0.0"
28 | - rvm: 2.4.10
29 | env: "RAILS_VERSION=6.0.0"
30 | - rvm: 2.3.8
31 | env: "RAILS_VERSION=6.0.0"
32 | - rvm: 2.7.2
33 | env: "RAILS_VERSION=5.2.0"
34 | - rvm: 2.7.2
35 | env: "RAILS_VERSION=5.0.0"
36 | - rvm: 2.7.2
37 | env: "RAILS_VERSION=4.2.0"
38 | - rvm: 2.6.6
39 | env: "RAILS_VERSION=4.2.0"
40 | - rvm: 2.5.8
41 | env: "RAILS_VERSION=4.2.0"
42 | - rvm: 2.4.10
43 | env: "RAILS_VERSION=4.2.0"
44 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Version 3.0.0
2 | * Added Foundation 6 support
3 | * Added auto_labels config
4 |
5 | ## Version 2.0
6 | This will be used for Foundation 5 support
7 |
8 | ### Breaking changes:
9 | * Dropped support for Ruby 1.9.3
10 | * display_flash_messages now requires the key_matching hash to be prefixed with the keyword argument :key_matching
11 |
12 | ### Features:
13 | * Add Rubocop code style linting
14 |
15 | ## Version 1.2.2
16 | * Fix Rails 5 deprecation warnings about alias_method_chain
17 | * Allow Capybara to be upgraded beyond 2.6.x
18 |
19 | ## Version 1.2.1
20 | * Lock Capybara at 2.6.x
21 |
22 | ## Version 1.2.0
23 | * Allow Rails 5 to be used
24 |
25 | ## Version 1.1.0
26 | * Form Helper: [Prefix and
27 | Postfix](http://foundation.zurb.com/sites/docs/v/5.5.3/components/forms.html#pre-postfix-labels-amp-actions) are now supported (PR #104 thanks djkz)
28 | * Flash Helper: a list of keys to be ignored can be specified - e.g. keys which are only used internally to pass data, not user-viewable messages(fix for #98)
29 | * FIX: Hints are added as span elements (PR #96 thanks collimarco)
30 | * FIX: Labels and fields don't have empty class attributes or repeated error classes
31 | (thanks collimarco)
32 | * FIX: Radio buttons don't have the `label="false"` on them when `label:
33 | false` is set (PR #107 thanks frenkel)
34 |
35 | ## Version 1.0.0
36 |
37 | * Released Feb 03rd 2015
38 | * Added configuration option to set button class in an initializer
39 | * Updated to be compatible with Foundation 5.2.2
40 | * Bugfixes
41 |
42 | ## Version 0.5.0
43 |
44 | * Released Oct 10th 2014
45 | * Bugfixes
46 |
47 | ## Version 0.4
48 |
49 | * Not released
50 | * Compatibility with Rails 4
51 | * Bugfixes
52 |
53 | ## Version 0.3
54 |
55 | * Not released
56 | * Mostly bugfixes
57 |
58 | ## Version 0.2.1
59 |
60 | * First production release (Jan 14th, 2012)
61 |
62 | ## Version 0.1.rc
63 |
64 | * initial release candidate (Jan 14th, 2012)
65 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ### We love pull requests. Here's a quick guide.
4 |
5 | Fork, then clone the repo:
6 | ```
7 | git clone git@github.com:your-username/foundation_rails_helper.git
8 | ```
9 |
10 | Set up your machine:
11 | ```
12 | git checkout -b your-branch-name
13 | bundle
14 | ```
15 |
16 | Make sure the tests pass:
17 | ```
18 | rake
19 | ```
20 |
21 | Make your change. Add tests for your change. Make the tests pass:
22 | ```
23 | rake
24 | ```
25 |
26 | Push to your fork and [submit a pull request][pr].
27 | ```
28 | git push origin your-branch-name
29 | ```
30 |
31 | [pr]: https://github.com/sgruhier/foundation_rails_helper/compare/
32 |
33 | At this point you're waiting on us. We like to at least comment on pull requests within three business days (and, typically, one business day). We may suggest some changes or improvements or alternatives.
34 |
35 | Some things that will increase the chance that your pull request is accepted:
36 |
37 | * Write tests.
38 | * Write a [good commit message][commit].
39 |
40 | [commit]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
41 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | source 'https://rubygems.org'
3 |
4 | # Specify your gem's dependencies in foundation_rails_helper.gemspec
5 | gemspec
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Sébastien Gruhier
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Foundation Rails Helper [](http://travis-ci.org/sgruhier/foundation_rails_helper)
2 |
3 | Gem for Rails 4.1+ applications that use the excellent [Zurb Foundation framework](https://github.com/zurb/foundation-rails).
4 |
5 | Includes:
6 |
7 | * A custom FormBuilder that generates a form using the Foundation framework classes. It replaces the current `form_for`, so there is no need to change your Rails code. Error messages are properly displayed.
8 |
9 | * A `display_flash_messages` helper method that uses Zurb Foundation Callout UI.
10 |
11 | ## Installation
12 |
13 | Add this line to your application's Gemfile:
14 |
15 | ```ruby
16 | gem 'foundation-rails', '~> 6.0' # required
17 | gem 'foundation_rails_helper', '~> 3.0'
18 | ```
19 |
20 | And then execute:
21 |
22 | ```bash
23 | $ bundle
24 | ```
25 |
26 | ## Compatibility
27 |
28 | * Only Rails 4.1/4.2/5/6, and Foundation 6 are fully supported
29 | * Some features may work with Foundation 5 and older, but results may vary, and markup which exists only for those versions will be gradually removed
30 | * Legacy branches exist for Rails 3, 4.0, and Foundation 5 (see the rails3, rails4.0, and foundation-5 branches). These are not actively supported, and fixes are not retroactively applied, but pull requests are welcome.
31 | * We test against ruby versions 2.1 and up. This gem may still work fine on 1.9.3, but your mileage may vary
32 |
33 |
34 | ## Screenshots
35 |
36 | ### Forms
37 | A classic devise sign up view will look like this:
38 |
39 | ```erb
40 | <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
41 | <%= f.email_field :email %>
42 | <%= f.password_field :password %>
43 | <%= f.password_field :password_confirmation %>
44 |
45 | <%= f.submit %>
46 | <% end %>
47 | ```
48 |
49 |
50 |
51 |
52 |
Form
53 |
Form with errors
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | ### Flash messages
69 |
70 | 
71 |
72 | ## Usage
73 |
74 | ### Flash Messages
75 |
76 | To use the built in flash helper, add `<%= display_flash_messages %>` to your layout file (eg. *app/views/layouts/application.html.erb*).
77 |
78 | ### form_for
79 |
80 | Form_for wraps the standard rails form_for helper.
81 |
82 | ```erb
83 | <%= form_for @user do |f| %>
84 | ...
85 | <% end %>
86 | ```
87 |
88 | generates:
89 |
90 | ```html
91 |
94 | ```
95 |
96 | ### text_field and Field Helpers
97 |
98 | Field helpers add a label element and an input of the proper type.
99 |
100 | ```ruby
101 | f.text_field :name
102 | ```
103 |
104 | generates:
105 |
106 | ```html
107 |
108 |
109 | ```
110 |
111 | Preventing the generation of labels can be accomplished three ways. To disable on a form element:
112 | ```ruby
113 | f.text_field :name, label: false
114 | ```
115 | For all form elements, add the option: `auto_labels: false` to the form helper. To disable for all forms in you project, use the `auto_labels` config option, see the Configuration section for more information.
116 |
117 | Change the label text and add a class on the label:
118 |
119 | ```ruby
120 | f.text_field :name, label: 'Nombre', label_options: { class: 'large' }
121 | ```
122 |
123 | If the help_text option is specified
124 |
125 | ```ruby
126 | f.text_field :name, help_text: "I'm a text field"
127 | ```
128 |
129 | an additional p element will be added after the input element:
130 |
131 | ```html
132 |
I'm a text field
133 | ```
134 |
135 | ### Submit Button
136 |
137 | The 'submit' helper wraps the rails helper and sets the class attribute to "success button" by default.
138 |
139 | ```ruby
140 | f.submit
141 | ```
142 |
143 | generates:
144 |
145 | ```html
146 |
147 | ```
148 |
149 | Specify the class option to override the default classes.
150 |
151 | ### Errors
152 |
153 | On error,
154 |
155 | ```ruby
156 | f.email_field :email
157 | ```
158 |
159 | generates:
160 |
161 | ```html
162 |
163 |
164 | can't be blank
165 | ```
166 |
167 | The class attribute of the 'small' element will mirror the class attribute of the 'input' element.
168 |
169 | If the `html_safe_errors: true` option is specified on a field, then any HTML you may have embedded in a custom error string will be displayed with the html_safe option.
170 |
171 | ### Prefix and Postfix
172 | Simple prefix and postfix span elements can be added beside inputs.
173 | ```ruby
174 | f.text_field :name, prefix { value: 'foo', small: 2, large: 3 }
175 | ```
176 | generates
177 | ```html
178 |
179 |
180 | foo
181 |
182 |
183 |
184 |
185 |
186 | ```
187 |
188 |
189 | ## Configuration
190 | Add an initializer file to your Rails app: *config/initializers/foundation_rails_helper.rb*
191 | containing the following block:
192 |
193 | ```ruby
194 | FoundationRailsHelper.configure do |config|
195 | # your options here
196 | end
197 | ```
198 |
199 | ### Submit Button Class
200 | To use a different class for the [submit button](https://github.com/sgruhier/foundation_rails_helper#submit-button) used in `form_for`, add a config named **button_class**:
201 | ```ruby
202 | # Default: 'success button'
203 | config.button_class = 'large hollow secondary button'
204 | ```
205 |
206 | Please note, the button class can still be overridden by an options hash.
207 |
208 | ### Ignored Flash Keys
209 | The flash helper assumes all flash entries are user-viewable messages.
210 | To exclude flash entries which are used for storing state
211 | (e.g. [Devise's `:timedout` flash](https://github.com/plataformatec/devise/issues/1777))
212 | you can specify a blacklist of keys to ignore with the **ignored_flash_keys** config option:
213 | ```ruby
214 | # Default: []
215 | config.ignored_flash_keys = [:timedout]
216 | ```
217 |
218 | ### Auto Labels
219 | If you prefer to not have the form builder automatically generate labels, set `auto_labels` to false.
220 | ```ruby
221 | # Default: true
222 | config.auto_labels = false
223 | ```
224 |
225 | ## Contributing
226 |
227 | See the [CONTRIBUTING](CONTRIBUTING.md) file.
228 |
229 | ## Copyright
230 |
231 | Sébastien Gruhier (http://xilinus.com) - MIT LICENSE
232 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | # frozen_string_literal: true
3 | require 'bundler/gem_tasks'
4 | require 'rspec/core/rake_task'
5 | require 'rubocop/rake_task'
6 |
7 | RSpec::Core::RakeTask.new(:spec)
8 | RuboCop::RakeTask.new(:rubocop)
9 | task default: [:spec, :rubocop]
10 |
--------------------------------------------------------------------------------
/foundation_rails_helper.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require File.expand_path('../lib/foundation_rails_helper/version', __FILE__)
3 |
4 | class Gem::Specification # rubocop:disable ClassAndModuleChildren
5 | def self.rails_gem_version
6 | # Allow different versions of the rails gems to be specified, for testing
7 | @rails_gem_version ||=
8 | ENV['RAILS_VERSION'] ? "~> #{ENV['RAILS_VERSION']}" : ['>= 4.1', '< 7.1']
9 | end
10 | end
11 |
12 | Gem::Specification.new do |gem|
13 | gem.authors = ['Sebastien Gruhier']
14 | gem.email = ['sebastien.gruhier@xilinus.com']
15 | gem.description =
16 | 'Rails for zurb foundation CSS framework. Form builder, flash message, ...'
17 | gem.summary = 'Rails helpers for zurb foundation CSS framework'
18 | gem.homepage = 'http://github.com/sgruhier/foundation_rails_helper'
19 |
20 | gem.executables =
21 | `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
22 | gem.files = `git ls-files`.split("\n")
23 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24 | gem.name = 'foundation_rails_helper'
25 | gem.require_paths = %w(lib)
26 | gem.version = FoundationRailsHelper::VERSION
27 | gem.license = 'MIT'
28 |
29 | gem.add_dependency 'railties', Gem::Specification.rails_gem_version
30 | gem.add_dependency 'actionpack', Gem::Specification.rails_gem_version
31 | gem.add_dependency 'activemodel', Gem::Specification.rails_gem_version
32 | gem.add_dependency 'activesupport', Gem::Specification.rails_gem_version
33 |
34 | gem.add_development_dependency 'rspec-rails', '~> 3.1'
35 | gem.add_development_dependency 'mime-types', '~> 2'
36 | gem.add_development_dependency 'capybara', '~> 2.7'
37 | gem.add_development_dependency 'rubocop', '~> 0.44.1'
38 | end
39 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'foundation_rails_helper/version'
3 | require 'foundation_rails_helper/configuration'
4 | require 'foundation_rails_helper/form_builder'
5 | require 'foundation_rails_helper/size_class_calculator'
6 | require 'foundation_rails_helper/flash_helper'
7 | require 'foundation_rails_helper/action_view_extension'
8 | ActiveSupport.on_load(:action_view) do
9 | include FoundationRailsHelper::FlashHelper
10 | end
11 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/action_view_extension.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | ActionView::Base.default_form_builder = FoundationRailsHelper::FormBuilder
3 | ActionView::Base.field_error_proc = proc do |html_tag, _instance_tag|
4 | html_tag
5 | end
6 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/configuration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | module FoundationRailsHelper
3 | class << self
4 | attr_writer :configuration
5 | end
6 |
7 | def self.configuration
8 | @configuration ||= Configuration.new
9 | end
10 |
11 | def self.reset
12 | @configuration = Configuration.new
13 | end
14 |
15 | def self.configure
16 | yield(configuration)
17 | end
18 |
19 | class Configuration
20 | attr_accessor :button_class
21 | attr_accessor :ignored_flash_keys
22 | attr_accessor :auto_labels
23 |
24 | def initialize
25 | @button_class = 'success button'
26 | @ignored_flash_keys = []
27 | @auto_labels = true
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/flash_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'action_view/helpers'
3 |
4 | module FoundationRailsHelper
5 | module FlashHelper
6 | #
7 | # This is an alert box.
8 | #
11 | #
12 | DEFAULT_KEY_MATCHING = {
13 | alert: :alert,
14 | notice: :success,
15 | info: :info,
16 | secondary: :secondary,
17 | success: :success,
18 | error: :alert,
19 | warning: :warning,
20 | primary: :primary
21 | }.freeze
22 |
23 | # Displays the flash messages found in ActionDispatch's +flash+ hash using
24 | # Foundation's +callout+ component.
25 | #
26 | # Parameters:
27 | # * +closable+ - A boolean to determine whether the displayed flash messages
28 | # should be closable by the user. Defaults to true.
29 | # * +key_matching+ - A Hash of key/value pairs mapping flash keys to the
30 | # corresponding class to use for the callout box.
31 | def display_flash_messages(closable: true, key_matching: {})
32 | key_matching = DEFAULT_KEY_MATCHING.merge(key_matching)
33 | key_matching.default = :primary
34 |
35 | capture do
36 | flash.each do |key, value|
37 | next if ignored_key?(key.to_sym)
38 |
39 | alert_class = key_matching[key.to_sym]
40 | concat alert_box(value, alert_class, closable)
41 | end
42 | end
43 | end
44 |
45 | private
46 |
47 | def alert_box(value, alert_class, closable)
48 | options = { class: "flash callout #{alert_class}" }
49 | options[:data] = { closable: '' } if closable
50 | content_tag(:div, options) do
51 | concat value
52 | concat close_link if closable
53 | end
54 | end
55 |
56 | def close_link
57 | button_tag(
58 | class: 'close-button',
59 | type: 'button',
60 | data: { close: '' },
61 | aria: { label: 'Dismiss alert' }
62 | ) do
63 | content_tag(:span, '×'.html_safe, aria: { hidden: true })
64 | end
65 | end
66 |
67 | def ignored_key?(key)
68 | FoundationRailsHelper.configuration.ignored_flash_keys.include?(key)
69 | end
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/form_builder.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'action_view/helpers'
3 |
4 | module FoundationRailsHelper
5 | class FormBuilder < ActionView::Helpers::FormBuilder
6 | include ActionView::Helpers::TagHelper
7 | include ActionView::Helpers::OutputSafetyHelper
8 | %w(file_field email_field text_field text_area telephone_field phone_field
9 | url_field number_field date_field datetime_field datetime_local_field
10 | month_field week_field time_field range_field search_field color_field)
11 | .each do |method_name|
12 | define_method(method_name) do |*args|
13 | attribute = args[0]
14 | options = args[1] || {}
15 | field(attribute, options) do |opts|
16 | super(attribute, opts)
17 | end
18 | end
19 | end
20 |
21 | def label(attribute, text = nil, options = {})
22 | if error?(attribute)
23 | options[:class] ||= ''
24 | options[:class] += ' is-invalid-label'
25 | end
26 |
27 | super(attribute, (text || '').html_safe, options)
28 | end
29 |
30 | # rubocop:disable LineLength
31 | def check_box(attribute, options = {}, checked_value = '1', unchecked_value = '0')
32 | custom_label(attribute, options[:label], options[:label_options]) do
33 | options.delete(:label)
34 | options.delete(:label_options)
35 | super(attribute, options, checked_value, unchecked_value)
36 | end + error_and_help_text(attribute, options)
37 | end
38 | # rubocop:enable LineLength
39 |
40 | def radio_button(attribute, tag_value, options = {})
41 | options[:label_options] ||= {}
42 | label_options = options.delete(:label_options).merge!(value: tag_value)
43 | label_text = options.delete(:label)
44 | l = label(attribute, label_text, label_options) unless label_text == false
45 | r = @template.radio_button(@object_name, attribute, tag_value,
46 | objectify_options(options))
47 |
48 | "#{r}#{l}".html_safe
49 | end
50 |
51 | def password_field(attribute, options = {})
52 | field attribute, options do |opts|
53 | super(attribute, opts.merge(autocomplete: :off))
54 | end
55 | end
56 |
57 | def datetime_select(attribute, options = {}, html_options = {})
58 | field attribute, options, html_options do |html_opts|
59 | super(attribute, options, html_opts.merge(autocomplete: :off))
60 | end
61 | end
62 |
63 | def date_select(attribute, options = {}, html_options = {})
64 | field attribute, options, html_options do |html_opts|
65 | super(attribute, options, html_opts.merge(autocomplete: :off))
66 | end
67 | end
68 |
69 | # rubocop:disable LineLength
70 | def time_zone_select(attribute, priorities = nil, options = {}, html_options = {})
71 | field attribute, options, html_options do |html_opts|
72 | super(attribute, priorities, options,
73 | html_opts.merge(autocomplete: :off))
74 | end
75 | end
76 | # rubocop:enable LineLength
77 |
78 | def select(attribute, choices, options = {}, html_options = {})
79 | field attribute, options, html_options do |html_opts|
80 | html_options[:autocomplete] ||= :off
81 | super(attribute, choices, options, html_opts)
82 | end
83 | end
84 |
85 | # rubocop:disable LineLength, ParameterLists
86 | def collection_select(attribute, collection, value_method, text_method, options = {}, html_options = {})
87 | field attribute, options, html_options do |html_opts|
88 | html_options[:autocomplete] ||= :off
89 | super(attribute, collection, value_method, text_method, options,
90 | html_opts)
91 | end
92 | end
93 |
94 | def grouped_collection_select(attribute, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
95 | field attribute, options, html_options do |html_opts|
96 | html_options[:autocomplete] ||= :off
97 | super(attribute, collection, group_method, group_label_method,
98 | option_key_method, option_value_method, options, html_opts)
99 | end
100 | end
101 | # rubocop:enable LineLength, ParameterLists
102 |
103 | def autocomplete(attribute, url, options = {})
104 | field attribute, options do |opts|
105 | opts.merge!(update_elements: opts[:update_elements],
106 | min_length: 0, value: object.send(attribute))
107 | autocomplete_field(attribute, url, opts)
108 | end
109 | end
110 |
111 | def submit(value = nil, options = {})
112 | options[:class] ||= FoundationRailsHelper.configuration.button_class
113 | super(value, options)
114 | end
115 |
116 | def error_for(attribute, options = {})
117 | return unless error?(attribute)
118 |
119 | class_name = 'form-error is-visible'
120 | class_name += " #{options[:class]}" if options[:class]
121 |
122 | error_messages = object.errors[attribute].join(', ')
123 | error_messages = error_messages.html_safe if options[:html_safe_errors]
124 | content_tag(:small, error_messages,
125 | class: class_name.sub('is-invalid-input', ''))
126 | end
127 |
128 | private
129 |
130 | def error?(attribute)
131 | object.respond_to?(:errors) && !object.errors[attribute].blank?
132 | end
133 |
134 | def default_label_text(object, attribute)
135 | if object.class.respond_to?(:human_attribute_name)
136 | return object.class.human_attribute_name(attribute)
137 | end
138 |
139 | attribute.to_s.humanize
140 | end
141 |
142 | def custom_label(attribute, text, options)
143 | return block_given? ? yield.html_safe : ''.html_safe if text == false
144 |
145 | text = default_label_text(object, attribute) if text.nil? || text == true
146 | text = safe_join([yield, text.html_safe]) if block_given?
147 | label(attribute, text, options || {})
148 | end
149 |
150 | def column_classes(options)
151 | classes = SizeClassCalculator.new(options).classes
152 | classes + ' columns'
153 | end
154 |
155 | def tag_from_options(name, options)
156 | return ''.html_safe unless options && options[:value].present?
157 |
158 | content_tag(:div,
159 | content_tag(:span, options[:value], class: name),
160 | class: column_classes(options).to_s)
161 | end
162 |
163 | def decrement_input_size(input, column, options)
164 | return unless options.present? && options.key?(column)
165 |
166 | input.send("#{column}=",
167 | (input.send(column) - options.fetch(column).to_i))
168 | input.send('changed?=', true)
169 | end
170 |
171 | def calculate_input_size(prefix_options, postfix_options)
172 | input_size =
173 | OpenStruct.new(changed?: false, small: 12, medium: 12, large: 12)
174 | %w(small medium large).each do |size|
175 | decrement_input_size(input_size, size.to_sym, prefix_options)
176 | decrement_input_size(input_size, size.to_sym, postfix_options)
177 | end
178 |
179 | input_size
180 | end
181 |
182 | def wrap_prefix_and_postfix(block, prefix_options, postfix_options)
183 | prefix = tag_from_options('prefix', prefix_options)
184 | postfix = tag_from_options('postfix', postfix_options)
185 |
186 | input_size = calculate_input_size(prefix_options, postfix_options)
187 | klass = column_classes(input_size.marshal_dump).to_s
188 | input = content_tag(:div, block, class: klass)
189 |
190 | return block unless input_size.changed?
191 | content_tag(:div, prefix + input + postfix, class: 'row collapse')
192 | end
193 |
194 | def error_and_help_text(attribute, options = {})
195 | html = ''
196 | if options[:help_text]
197 | html += content_tag(:p, options[:help_text], class: 'help-text')
198 | end
199 | html += error_for(attribute, options) || ''
200 | html.html_safe
201 | end
202 |
203 | def field_label(attribute, options)
204 | return ''.html_safe unless auto_labels || options[:label]
205 | custom_label(attribute, options[:label], options[:label_options])
206 | end
207 |
208 | def field(attribute, options, html_options = nil)
209 | html = field_label(attribute, options)
210 | class_options = html_options || options
211 |
212 | if error?(attribute)
213 | class_options[:class] = class_options[:class].to_s
214 | class_options[:class] += ' is-invalid-input'
215 | end
216 |
217 | options.delete(:label)
218 | options.delete(:label_options)
219 | help_text = options.delete(:help_text)
220 | prefix = options.delete(:prefix)
221 | postfix = options.delete(:postfix)
222 |
223 | html += wrap_prefix_and_postfix(yield(class_options), prefix, postfix)
224 | html + error_and_help_text(attribute, options.merge(help_text: help_text))
225 | end
226 |
227 | def auto_labels
228 | if @options[:auto_labels].nil?
229 | FoundationRailsHelper.configuration.auto_labels
230 | else
231 | @options[:auto_labels]
232 | end
233 | end
234 | end
235 | end
236 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/size_class_calculator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | module FoundationRailsHelper
3 | class SizeClassCalculator
4 | def initialize(size_options)
5 | @small = size_options[:small]
6 | @medium = size_options[:medium]
7 | @large = size_options[:large]
8 | end
9 |
10 | def classes
11 | [small_class, medium_class, large_class].compact.join(' ')
12 | end
13 |
14 | private
15 |
16 | def small_class
17 | "small-#{@small}" if valid_size(@small)
18 | end
19 |
20 | def medium_class
21 | "medium-#{@medium}" if valid_size(@medium)
22 | end
23 |
24 | def large_class
25 | "large-#{@large}" if valid_size(@large)
26 | end
27 |
28 | def valid_size(value)
29 | value.present? && value.to_i < 12
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | module FoundationRailsHelper
3 | VERSION = '4.0.1'
4 | end
5 |
--------------------------------------------------------------------------------
/lib/railtie.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | module Railtie
3 | class Railtie < Rails::Railtie
4 | # initializer 'setup foundation form builder' do
5 | #
6 | # end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/spec/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from:
2 | - ../.rubocop.yml
3 |
4 | StringLiterals:
5 | EnforcedStyle: double_quotes
6 | Exclude:
7 | - './spec_helper.rb'
8 |
9 | Metrics/BlockLength:
10 | Enabled: false
11 |
--------------------------------------------------------------------------------
/spec/foundation_rails_helper/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require "spec_helper"
3 |
4 | describe FoundationRailsHelper do
5 | describe FoundationRailsHelper::Configuration do
6 | describe "#button_class" do
7 | it "default value is 'success button'" do
8 | config = FoundationRailsHelper::Configuration.new
9 | expect(config.button_class).to eq("success button")
10 | end
11 | end
12 |
13 | describe "#button_class=" do
14 | it "can set value" do
15 | config = FoundationRailsHelper::Configuration.new
16 | config.button_class = "new-class"
17 | expect(config.button_class).to eq("new-class")
18 | end
19 | end
20 |
21 | describe "#ignored_flash_keys" do
22 | it "defaults to empty" do
23 | config = FoundationRailsHelper::Configuration.new
24 | expect(config.ignored_flash_keys).to eq([])
25 | end
26 | end
27 |
28 | describe "#ignored_flash_keys=" do
29 | it "can set the value" do
30 | config = FoundationRailsHelper::Configuration.new
31 | config.ignored_flash_keys = [:foo]
32 | expect(config.ignored_flash_keys).to eq([:foo])
33 | end
34 | end
35 |
36 | describe "#auto_labels" do
37 | it "default value is 'true'" do
38 | config = FoundationRailsHelper::Configuration.new
39 | expect(config.auto_labels).to be(true)
40 | end
41 | end
42 |
43 | describe "#auto_labels=" do
44 | it "can set the value" do
45 | config = FoundationRailsHelper::Configuration.new
46 | config.auto_labels = false
47 | expect(config.auto_labels).to be(false)
48 | end
49 | end
50 |
51 | describe ".reset" do
52 | it "resets the configured button class" do
53 | FoundationRailsHelper.configure do |config|
54 | config.button_class = "new-class"
55 | end
56 |
57 | FoundationRailsHelper.reset
58 |
59 | config = FoundationRailsHelper.configuration
60 | expect(config.button_class).to eq("success button")
61 | end
62 |
63 | it "resets the configured ignored flash keys" do
64 | FoundationRailsHelper.configure do |config|
65 | config.ignored_flash_keys = [:new_key]
66 | end
67 |
68 | FoundationRailsHelper.reset
69 |
70 | config = FoundationRailsHelper.configuration
71 | expect(config.ignored_flash_keys).to eq([])
72 | end
73 |
74 | it "resets the configured auto labels" do
75 | FoundationRailsHelper.configure do |config|
76 | config.auto_labels = false
77 | end
78 |
79 | FoundationRailsHelper.reset
80 |
81 | config = FoundationRailsHelper.configuration
82 | expect(config.auto_labels).to be(true)
83 | end
84 | end
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/spec/foundation_rails_helper/flash_helper_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require "spec_helper"
3 |
4 | describe FoundationRailsHelper::FlashHelper do
5 | include ActionView::Context if defined?(ActionView::Context)
6 | include ActionView::Helpers::FormTagHelper
7 | include FoundationRailsHelper::FlashHelper
8 |
9 | KEY_MATCHING = FoundationRailsHelper::FlashHelper::DEFAULT_KEY_MATCHING.freeze
10 |
11 | KEY_MATCHING.each do |type, klass|
12 | it "displays flash message with #{klass} class for #{type} message" do
13 | allow(self).to receive(:flash).and_return(type.to_s => "Flash message")
14 | node = Capybara.string display_flash_messages
15 | expect(node)
16 | .to have_css("div.flash.callout.#{klass}", text: "Flash message")
17 | .and have_css("[data-close]", text: "×")
18 | end
19 | end
20 |
21 | it "handles symbol keys" do
22 | allow(self).to receive(:flash).and_return(success: "Flash message")
23 | node = Capybara.string display_flash_messages
24 | expect(node).to have_css("div.callout.success", text: "Flash message")
25 | end
26 |
27 | it "handles string keys" do
28 | allow(self).to receive(:flash).and_return("success" => "Flash message")
29 | node = Capybara.string display_flash_messages
30 | expect(node).to have_css("div.callout.success", text: "Flash message")
31 | end
32 |
33 | it "displays multiple flash messages" do
34 | allow(self).to receive(:flash)
35 | .and_return("success" => "Yay it worked",
36 | "error" => "But this other thing failed")
37 | node = Capybara.string display_flash_messages
38 | expect(node)
39 | .to have_css("div.callout.success", text: "Yay it worked")
40 | .and have_css("div.callout.alert", text: "But this other thing failed")
41 | end
42 |
43 | it "displays flash message with overridden key matching" do
44 | allow(self).to receive(:flash).and_return("notice" => "Flash message")
45 | node =
46 | Capybara.string display_flash_messages(key_matching: { notice: :alert })
47 | expect(node).to have_css("div.callout.alert", text: "Flash message")
48 | end
49 |
50 | it "displays flash message with custom key matching" do
51 | allow(self).to receive(:flash).and_return("custom_type" => "Flash message")
52 | node = Capybara.string(
53 | display_flash_messages(key_matching: { custom_type: :custom_class })
54 | )
55 | expect(node).to have_css("div.callout.custom_class", text: "Flash message")
56 | end
57 |
58 | it "displays flash message with standard class if key doesn't match" do
59 | allow(self).to receive(:flash).and_return("custom_type" => "Flash message")
60 | node = Capybara.string display_flash_messages
61 | expect(node).to have_css("div.callout.primary", text: "Flash message")
62 | end
63 |
64 | context "when the flash hash contains devise internal data" do
65 | before do
66 | FoundationRailsHelper.configure do |config|
67 | config.ignored_flash_keys += [:timedout]
68 | end
69 | end
70 |
71 | it "doesn't raise an error (e.g. NoMethodError)" do
72 | allow(self).to receive(:flash).and_return("timedout" => true)
73 | expect { Capybara.string display_flash_messages }.not_to raise_error
74 | end
75 |
76 | it "doesn't display an alert for that data" do
77 | allow(self).to receive(:flash).and_return("timedout" => true)
78 | expect(display_flash_messages).to be_nil
79 |
80 | # Ideally we'd create a node using Capybara.string, as in the other
81 | # examples and set the following expectation:
82 | # expect(node).to_not have_css("div.callout")
83 | # but Capybara.string doesn't behave nicely with nil input:
84 | # the input gets assigned to the @native instance variable,
85 | # which is used by the css matcher, so we get the following error:
86 | # undefined method `css' for nil:NilClass
87 | end
88 | end
89 |
90 | context "with (closable: false) option" do
91 | it "doesn't display the close button" do
92 | allow(self).to receive(:flash).and_return(success: "Flash message")
93 | node = Capybara.string display_flash_messages(closable: false)
94 | expect(node)
95 | .to have_css("div.flash.callout.success", text: "Flash message")
96 | .and have_no_css("[data-close]", text: "×")
97 | end
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/spec/foundation_rails_helper/form_builder_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require "spec_helper"
3 |
4 | describe "FoundationRailsHelper::FormHelper" do
5 | include FoundationRailsSpecHelper
6 |
7 | before do
8 | mock_everything
9 | end
10 |
11 | it "should have FoundationRailsHelper::FormHelper as default buidler" do
12 | form_for(@author) do |builder|
13 | expect(builder.class).to eq FoundationRailsHelper::FormBuilder
14 | end
15 | end
16 |
17 | it "should display labels by default" do
18 | form_for(@author) do |builder|
19 | node = Capybara.string builder.text_field(:login)
20 | expect(node).to have_css('label[for="author_login"]', text: "Login")
21 | end
22 | end
23 |
24 | it "should display labels if auto_labels option is true" do
25 | form_for(@author, auto_labels: true) do |builder|
26 | node = Capybara.string builder.text_field(:login)
27 | expect(node).to have_css('label[for="author_login"]', text: "Login")
28 | end
29 | end
30 |
31 | it "should display labels if no auto_labels option" do
32 | form_for(@author, html: { class: "myclass" }) do |builder|
33 | node = Capybara.string builder.text_field(:login)
34 | expect(node).to have_css('label[for="author_login"]', text: "Login")
35 | end
36 | end
37 |
38 | it "shouldn't display labels if auto_labels option is false" do
39 | options = { html: { class: "myclass" }, auto_labels: false }
40 |
41 | form_for(@author, options) do |builder|
42 | node = Capybara.string builder.text_field(:login)
43 | expect(node).to_not have_css('label[for="author_login"]', text: "Login")
44 | end
45 | end
46 |
47 | it "should display labels if :auto_labels is set to nil" do
48 | form_for(@author, auto_labels: nil) do |builder|
49 | node = Capybara.string builder.text_field(:login)
50 | expect(node).to have_css('label[for="author_login"]', text: "Login")
51 | end
52 | end
53 |
54 | it "should display labels if :auto_labels is set to a string" do
55 | form_for(@author, auto_labels: "false") do |builder|
56 | node = Capybara.string builder.text_field(:login)
57 | expect(node).to have_css('label[for="author_login"]', text: "Login")
58 | end
59 | end
60 |
61 | it "shouldn't display labels if :auto_labels false at configuration time" do
62 | allow(FoundationRailsHelper)
63 | .to receive_message_chain(:configuration, :auto_labels).and_return(false)
64 |
65 | form_for(@author) do |builder|
66 | node = Capybara.string builder.text_field(:login)
67 | expect(node).not_to have_css('label[for="author_login"]', text: "Login")
68 | end
69 | end
70 |
71 | describe "label" do
72 | context "when there aren't any errors and no class option is passed" do
73 | it "should not have a class attribute" do
74 | form_for(@author) do |builder|
75 | node = Capybara.string builder.text_field(:login)
76 | expect(node).to have_css('label:not([class=""])')
77 | end
78 | end
79 | end
80 |
81 | it "should not have error class multiple times" do
82 | form_for(@author) do |builder|
83 | allow(@author).to receive(:errors).and_return(login: ["required"])
84 | node = Capybara.string builder.text_field(:login)
85 | error_class = node.find("label")["class"].split(/\s+/).keep_if do |v|
86 | v == "is-invalid-label"
87 | end
88 | expect(error_class.size).to eq 1
89 | end
90 | end
91 | end
92 |
93 | describe "prefix" do
94 | context "when input field has a prefix" do
95 | before do
96 | prefix = { small: 2, medium: 4, large: 6, value: "Prefix" }
97 |
98 | form_for(@author) do |builder|
99 | @node = Capybara.string builder.text_field(:login, prefix: prefix)
100 | end
101 | end
102 |
103 | it "wraps input in the div with class 'row collapse'" do
104 | expect(@node.find(".row.collapse")).to_not be nil
105 | end
106 |
107 | it "wraps prefix in the div with the right column size" do
108 | expect(@node.find(".row.collapse"))
109 | .to have_css("div.small-2.medium-4.large-6.columns")
110 | end
111 |
112 | it "creates prefix span with right value" do
113 | selector = "div.small-2.medium-4.large-6.columns"
114 | expect(@node.find(".row.collapse").find(selector).find("span").text)
115 | .to eq("Prefix")
116 | end
117 |
118 | it "creates prefix span with right class" do
119 | expect(@node.find(".row.collapse")).to have_css("span.prefix")
120 | end
121 |
122 | it "wraps input in the div with the right column size" do
123 | expect(@node.find(".row.collapse"))
124 | .to have_css("div.small-10.medium-8.large-6.columns")
125 | end
126 |
127 | it "has right value for the input" do
128 | selector = "div.small-10.medium-8.large-6.columns"
129 |
130 | expect(@node.find(".row.collapse").find(selector))
131 | .to have_css('input[type="text"][name="author[login]"]')
132 | end
133 | end
134 |
135 | context "without prefix" do
136 | it "will not wrap input into a div" do
137 | form_for(@author) do |builder|
138 | node = Capybara.string builder.text_field(:login)
139 | expect(node).to_not have_css("div.row.collapse")
140 | end
141 | end
142 | end
143 | end
144 |
145 | describe "postfix" do
146 | context "when input field has a postfix" do
147 | before do
148 | postfix = { small: 2, medium: 4, large: 6, value: "Postfix" }
149 |
150 | form_for(@author) do |builder|
151 | @node = Capybara.string builder.text_field(:login, postfix: postfix)
152 | end
153 | end
154 |
155 | it "wraps input in the div with class 'row collapse'" do
156 | expect(@node.find(".row.collapse")).to_not be nil
157 | end
158 |
159 | it "wraps postfix in the div with the right column size" do
160 | expect(@node.find(".row.collapse"))
161 | .to have_css("div.small-2.medium-4.large-6.columns")
162 | end
163 |
164 | it "creates postfix span with right value" do
165 | selector = "div.small-2.medium-4.large-6.columns"
166 |
167 | expect(@node.find(".row.collapse").find(selector).find("span").text)
168 | .to eq("Postfix")
169 | end
170 |
171 | it "creates postfix span with right class" do
172 | expect(@node.find(".row.collapse")).to have_css("span.postfix")
173 | end
174 |
175 | it "wraps input in the div with the right column size" do
176 | expect(@node.find(".row.collapse"))
177 | .to have_css("div.small-10.medium-8.large-6.columns")
178 | end
179 |
180 | it "has right value for the input" do
181 | selector = "div.small-10.medium-8.large-6.columns"
182 |
183 | expect(@node.find(".row.collapse").find(selector))
184 | .to have_css('input[type="text"][name="author[login]"]')
185 | end
186 | end
187 |
188 | context "with only one column size" do
189 | before do
190 | form_for(@author) do |builder|
191 | @small_node = Capybara.string(
192 | builder.text_field(:login, postfix: { small: 2, value: "Postfix" })
193 | )
194 | @medium_node = Capybara.string(
195 | builder.text_field(:login, postfix: { medium: 2, value: "Postfix" })
196 | )
197 | @large_node = Capybara.string(
198 | builder.text_field(:login, postfix: { large: 2, value: "Postfix" })
199 | )
200 | end
201 | end
202 |
203 | it "wraps postfix in the div with the right column size" do
204 | expect(@small_node.find(".row.collapse"))
205 | .to have_css("div.small-2.columns")
206 | expect(@medium_node.find(".row.collapse"))
207 | .to have_css("div.medium-2.columns")
208 | expect(@large_node.find(".row.collapse"))
209 | .to have_css("div.large-2.columns")
210 | end
211 |
212 | it "wraps input in the div with the right column size" do
213 | expect(@small_node.find(".row.collapse"))
214 | .to have_css("div.small-10.columns")
215 | expect(@medium_node.find(".row.collapse"))
216 | .to have_css("div.medium-10.columns")
217 | expect(@large_node.find(".row.collapse"))
218 | .to have_css("div.large-10.columns")
219 | end
220 |
221 | it "excludes other classes from the prefix" do
222 | expect(@small_node.find(".row.collapse"))
223 | .to_not have_css("div.medium-2.columns")
224 | expect(@small_node.find(".row.collapse"))
225 | .to_not have_css("div.large-2.columns")
226 | end
227 |
228 | it "excludes other classes from the input" do
229 | expect(@small_node.find(".row.collapse"))
230 | .to have_css("div.small-10.columns")
231 | expect(@small_node.find(".row.collapse"))
232 | .to_not have_css("div.medium-12.columns")
233 | expect(@small_node.find(".row.collapse"))
234 | .to_not have_css("div.large-12.columns")
235 | end
236 | end
237 | end
238 |
239 | describe "with both prefix and postfix" do
240 | context "when input field has a prefix" do
241 | before do
242 | prefix = { small: 2, medium: 3, large: 4, value: "Prefix" }
243 | postfix = { small: 2, medium: 3, large: 4, value: "Postfix" }
244 |
245 | form_for(@author) do |builder|
246 | @node = Capybara.string(
247 | builder.text_field(:login, prefix: prefix, postfix: postfix)
248 | )
249 | end
250 | end
251 |
252 | it "wraps input in the div with the right column size" do
253 | expect(@node.find(".row.collapse"))
254 | .to have_css("div.small-8.medium-6.large-4.columns")
255 | end
256 | end
257 | end
258 |
259 | describe "input generators" do
260 | it "should generate text_field input" do
261 | form_for(@author) do |builder|
262 | node = Capybara.string builder.text_field(:login)
263 | expect(node).to have_css('label[for="author_login"]', text: "Login")
264 | expect(node).to have_css('input[type="text"][name="author[login]"]')
265 | expect(node.find_field("author_login").value).to eq @author.login
266 | end
267 | end
268 |
269 | it "should generate text_field input without label" do
270 | form_for(@author) do |builder|
271 | node = Capybara.string builder.text_field(:login, label: false)
272 | expect(node).to_not have_css('label[for="author_login"]', text: "Login")
273 | expect(node).to have_css('input[type="text"][name="author[login]"]')
274 | expect(node.find_field("author_login").value).to eq @author.login
275 | end
276 | end
277 |
278 | it "should generate text_field with class from options" do
279 | form_for(@author) do |builder|
280 | node = Capybara.string builder.text_field(:login, class: "righteous")
281 | expect(node)
282 | .to have_css('input.righteous[type="text"][name="author[login]"]')
283 | end
284 | end
285 |
286 | it "should generate password_field input" do
287 | form_for(@author) do |builder|
288 | node = Capybara.string builder.password_field(:password)
289 | expect(node)
290 | .to have_css('label[for="author_password"]', text: "Password")
291 | expect(node)
292 | .to have_css('input[type="password"][name="author[password]"]')
293 | expect(node.find_field("author_password").value).to be_nil
294 | end
295 | end
296 |
297 | it "should generate email_field input" do
298 | form_for(@author) do |builder|
299 | node = Capybara.string builder.email_field(:email)
300 | expect(node).to have_css('label[for="author_email"]', text: "Email")
301 | expect(node).to have_css('input[type="email"][name="author[email]"]')
302 | expect(node.find_field("author_email").value).to eq @author.email
303 | end
304 | end
305 |
306 | it "should generate url_field input" do
307 | form_for(@author) do |builder|
308 | node = Capybara.string builder.url_field(:url)
309 | expect(node).to have_css('label[for="author_url"]', text: "Url")
310 | expect(node).to have_css('input[type="url"][name="author[url]"]')
311 | expect(node.find_field("author_url").value).to eq @author.url
312 | end
313 | end
314 |
315 | it "should generate phone_field input" do
316 | form_for(@author) do |builder|
317 | node = Capybara.string builder.phone_field(:phone)
318 | expect(node).to have_css('label[for="author_phone"]', text: "Phone")
319 | expect(node).to have_css('input[type="tel"][name="author[phone]"]')
320 | expect(node.find_field("author_phone").value).to eq @author.phone
321 | end
322 | end
323 |
324 | it "should generate number_field input" do
325 | form_for(@author) do |builder|
326 | node = Capybara.string builder.number_field(:some_number)
327 | expect(node)
328 | .to have_css('label[for="author_some_number"]', text: "Some number")
329 | expect(node)
330 | .to have_css('input[type="number"][name="author[some_number]"]')
331 | expect(node.find_field("author_some_number").value)
332 | .to eq @author.some_number
333 | end
334 | end
335 |
336 | it "should generate text_area input" do
337 | form_for(@author) do |builder|
338 | node = Capybara.string builder.text_area(:description)
339 | expect(node)
340 | .to have_css('label[for="author_description"]', text: "Description")
341 | expect(node).to have_css('textarea[name="author[description]"]')
342 | expect(node.find_field("author_description").value.strip)
343 | .to eq @author.description
344 | end
345 | end
346 |
347 | it "should generate file_field input" do
348 | form_for(@author) do |builder|
349 | node = Capybara.string builder.file_field(:avatar)
350 | expect(node).to have_css('label[for="author_avatar"]', text: "Avatar")
351 | expect(node).to have_css('input[type="file"][name="author[avatar]"]')
352 | expect(node.find_field("author_avatar").value).to be_nil
353 | end
354 | end
355 |
356 | it "should generate select input" do
357 | choices = [["Choice #1", :a], ["Choice #2", :b]]
358 |
359 | form_for(@author) do |builder|
360 | node = Capybara.string builder.select(:description, choices)
361 | expect(node)
362 | .to have_css('label[for="author_description"]', text: "Description")
363 | expect(node).to have_css('select[name="author[description]"]')
364 | expect(node)
365 | .to have_css('select[name="author[description]"] option[value="a"]',
366 | text: "Choice #1")
367 | expect(node)
368 | .to have_css('select[name="author[description]"] option[value="b"]',
369 | text: "Choice #2")
370 | end
371 | end
372 |
373 | it "should generate check_box input" do
374 | label = 'label[for="author_active"]'
375 | name = '[name="author[active]"]'
376 |
377 | form_for(@author) do |builder|
378 | node = Capybara.string builder.check_box(:active)
379 | expect(node).to have_css(
380 | "#{label} input[type=\"hidden\"]#{name}[value=\"0\"]",
381 | visible: false
382 | )
383 | expect(node)
384 | .to have_css("#{label} input[type=\"checkbox\"]#{name}")
385 | expect(node).to have_css(label, text: "Active")
386 | end
387 | end
388 |
389 | it "should generate check_box input without a label" do
390 | form_for(@author) do |builder|
391 | node = Capybara.string builder.check_box(:active, label: false)
392 | expect(node)
393 | .to have_css('input[type="hidden"][name="author[active]"][value="0"]',
394 | visible: false)
395 | expect(node)
396 | .to have_css('input[type="checkbox"][name="author[active]"]')
397 | expect(node).to_not have_css('label[for="author_active"]')
398 | end
399 | end
400 |
401 | it "should generate check_box input with a label with HTML content" do
402 | label_text = "Accepts terms and conditions"
403 |
404 | form_for(@author) do |builder|
405 | node = Capybara.string(
406 | builder.check_box(:active, label: "#{label_text}")
407 | )
408 |
409 | expect(node)
410 | .to have_css('label[for="author_active"] a', text: label_text)
411 | end
412 | end
413 |
414 | it "should generate radio_button input" do
415 | form_for(@author) do |builder|
416 | node = Capybara.string builder.radio_button(:active, "ok")
417 | expect(node).to have_css('label[for="author_active_ok"]')
418 | expect(node).to have_css('input[type="radio"][name="author[active]"]')
419 | end
420 | end
421 |
422 | it "should generate radio_button input with a label" do
423 | form_for(@author) do |builder|
424 | node = Capybara.string(
425 | builder.radio_button(:active, true, label: "Functioning")
426 | )
427 | expect(node)
428 | .to have_css('label[for="author_active_true"]', text: "Functioning")
429 | expect(node)
430 | .to have_css('input[type="radio"][name="author[active]"]')
431 | end
432 | end
433 |
434 | it "should generate radio_button without a label" do
435 | form_for(@author) do |builder|
436 | node = Capybara.string builder.radio_button(:active, "ok", label: false)
437 | expect(node).to_not have_css('label[for="author_active_ok"]')
438 | expect(node).to_not have_css('input[label="false"]')
439 | expect(node).to have_css('input[type="radio"][name="author[active]"]')
440 | end
441 | end
442 |
443 | it "should generate radio_button with label options" do
444 | form_for(@author) do |builder|
445 | node = Capybara.string(
446 | builder.radio_button(
447 | :active,
448 | "ok",
449 | class: "very",
450 | label_options: { class: "special" }
451 | )
452 | )
453 |
454 | expect(node).to have_css('label.special[for="author_active_ok"]')
455 | expect(node)
456 | .to have_css('input.very[type="radio"][name="author[active]"]')
457 | end
458 | end
459 |
460 | it "should generate date_select input" do
461 | select = "select#author_birthdate_"
462 | option = 'option[selected="selected"]'
463 |
464 | form_for(@author) do |builder|
465 | node = Capybara.string(
466 | builder.label(:birthdate) + builder.date_select(:birthdate)
467 | )
468 | expect(node)
469 | .to have_css('label[for="author_birthdate"]', text: "Birthdate")
470 | %w(1 2 3).each do |i|
471 | expect(node).to have_css("select[name='author[birthdate(#{i}i)]']")
472 | end
473 | expect(node)
474 | .to have_css("#{select}1i #{option}[value=\"1969\"]")
475 | expect(node)
476 | .to have_css("#{select}2i #{option}[value=\"6\"]")
477 | expect(node)
478 | .to have_css("#{select}3i #{option}[value=\"18\"]")
479 | %w(4 5).each do |i|
480 | expect(node)
481 | .to_not have_css("select[name='author[birthdate(#{i}i)]']")
482 | end
483 | end
484 | end
485 |
486 | it "should generate date_select input with :discard_year => true" do
487 | select = "select#author_birthdate_"
488 | option = 'option[selected="selected"]'
489 |
490 | form_for(@author) do |builder|
491 | node = Capybara.string(
492 | builder.label(:birthdate) +
493 | builder.date_select(:birthdate, discard_year: true)
494 | )
495 | expect(node)
496 | .to have_css('label[for="author_birthdate"]', text: "Birthdate")
497 | %w(2 3).each do |i|
498 | expect(node).to have_css("select[name='author[birthdate(#{i}i)]']")
499 | end
500 | expect(node).to_not have_css("#{select}1i #{option}[value=\"1969\"]")
501 | expect(node).to have_css("#{select}2i #{option}[value=\"6\"]")
502 | expect(node).to have_css("#{select}3i #{option}[value=\"18\"]")
503 | %w(1 4 5).each do |i|
504 | expect(node)
505 | .to_not have_css("select[name='author[birthdate(#{i}i)]']")
506 | end
507 | end
508 | end
509 |
510 | it "should generate datetime_select input" do
511 | select = "select#author_birthdate_"
512 | option = 'option[selected="selected"]'
513 |
514 | form_for(@author) do |builder|
515 | node = Capybara.string(
516 | builder.label(:birthdate) +
517 | builder.datetime_select(:birthdate)
518 | )
519 | expect(node)
520 | .to have_css('label[for="author_birthdate"]', text: "Birthdate")
521 | %w(1 2 3 4 5).each do |i|
522 | expect(node).to have_css("select[name='author[birthdate(#{i}i)]']")
523 | end
524 | expect(node).to have_css("#{select}1i #{option}[value=\"1969\"]")
525 | expect(node).to have_css("#{select}2i #{option}[value=\"6\"]")
526 | expect(node).to have_css("#{select}3i #{option}[value=\"18\"]")
527 | expect(node).to have_css("#{select}4i #{option}[value=\"20\"]")
528 | expect(node).to have_css("#{select}5i #{option}[value=\"30\"]")
529 | end
530 | end
531 |
532 | it "should generate datetime_select input with :discard_year => true" do
533 | select = "select#author_birthdate_"
534 | option = 'option[selected="selected"]'
535 |
536 | form_for(@author) do |builder|
537 | node = Capybara.string(
538 | builder.label(:birthdate) +
539 | builder.datetime_select(:birthdate, discard_year: true)
540 | )
541 | expect(node)
542 | .to have_css('label[for="author_birthdate"]', text: "Birthdate")
543 | %w(2 3 4 5).each do |i|
544 | expect(node).to have_css("select[name='author[birthdate(#{i}i)]']")
545 | end
546 | expect(node).to_not have_css("#{select}1i #{option}[value=\"1969\"]")
547 | expect(node).to have_css("#{select}2i #{option}[value=\"6\"]")
548 | expect(node).to have_css("#{select}3i #{option}[value=\"18\"]")
549 | expect(node).to have_css("#{select}4i #{option}[value=\"20\"]")
550 | expect(node).to have_css("#{select}5i #{option}[value=\"30\"]")
551 | expect(node).to_not have_css("select[name='author[birthdate(#1i)]']")
552 | end
553 | end
554 |
555 | it "should generate time_zone_select input" do
556 | form_for(@author) do |builder|
557 | node = Capybara.string(
558 | builder.label(:time_zone) + builder.time_zone_select(:time_zone)
559 | )
560 | expect(node)
561 | .to have_css('label[for="author_time_zone"]', text: "Time zone")
562 | expect(node).to have_css('select[name="author[time_zone]"]')
563 | expect(node)
564 | .to have_css('select[name="author[time_zone]"] option[value="Perth"]',
565 | text: "(GMT+08:00) Perth")
566 | end
567 | end
568 |
569 | it "should generate date_field input" do
570 | form_for(@author) do |builder|
571 | node = Capybara.string builder.date_field(:publish_date)
572 | expect(node)
573 | .to have_css('label[for="author_publish_date"]', text: "date")
574 | expect(node)
575 | .to have_css('input[type="date"][name="author[publish_date]"]')
576 | expect(node.find_field("author_publish_date").value)
577 | .to eq @author.publish_date.to_s
578 | end
579 | end
580 |
581 | it "should generate datetime_field input" do
582 | form_for(@author) do |builder|
583 | node = Capybara.string builder.datetime_field(:forty_two)
584 | expect(node)
585 | .to have_css('label[for="author_forty_two"]', text: "Forty two")
586 | expect(node)
587 | .to have_css('input[type^="datetime"][name="author[forty_two]"]')
588 | value = DateTime.parse(node.find_field("author_forty_two").value)
589 | expect(value).to eq @author.forty_two.to_s
590 | end
591 | end
592 |
593 | it "should generate datetime_local_field" do
594 | form_for(@author) do |builder|
595 | node = Capybara.string builder.datetime_local_field(:forty_two)
596 | expect(node)
597 | .to have_css('label[for="author_forty_two"]', text: "Forty two")
598 | expect(node)
599 | .to have_css('input[type="datetime-local"][name="author[forty_two]"]')
600 | expect(node.find_field("author_forty_two").value)
601 | .to eq @author.forty_two.strftime("%Y-%m-%dT%H:%M:%S")
602 | end
603 | end
604 |
605 | it "should generate month_field input" do
606 | form_for(@author) do |builder|
607 | node = Capybara.string builder.month_field(:forty_two)
608 | expect(node)
609 | .to have_css('label[for="author_forty_two"]', text: "Forty two")
610 | expect(node)
611 | .to have_css('input[type="month"][name="author[forty_two]"]')
612 | expect(node.find_field("author_forty_two").value)
613 | .to eq @author.forty_two.strftime("%Y-%m")
614 | end
615 | end
616 |
617 | it "should generate week_field" do
618 | form_for(@author) do |builder|
619 | node = Capybara.string builder.week_field(:forty_two)
620 | expect(node)
621 | .to have_css('label[for="author_forty_two"]', text: "Forty two")
622 | expect(node)
623 | .to have_css('input[type="week"][name="author[forty_two]"]')
624 | expect(node.find_field("author_forty_two").value)
625 | .to eq @author.forty_two.strftime("%Y-W%V")
626 | end
627 | end
628 |
629 | it "should generate time_field" do
630 | form_for(@author) do |builder|
631 | node = Capybara.string builder.time_field(:forty_two)
632 | expect(node)
633 | .to have_css('label[for="author_forty_two"]', text: "Forty two")
634 | expect(node)
635 | .to have_css('input[type="time"][name="author[forty_two]"]')
636 | expect(node.find_field("author_forty_two").value)
637 | .to eq @author.forty_two.strftime("%H:%M:%S.%L")
638 | end
639 | end
640 |
641 | it "should generate range_field" do
642 | form_for(@author) do |builder|
643 | node = Capybara.string builder.range_field(:some_number)
644 | expect(node)
645 | .to have_css('label[for="author_some_number"]', text: "Some number")
646 | expect(node)
647 | .to have_css('input[type="range"][name="author[some_number]"]')
648 | expect(node.find_field("author_some_number").value)
649 | .to eq @author.some_number
650 | end
651 | end
652 |
653 | it "should generate search_field" do
654 | form_for(@author) do |builder|
655 | node = Capybara.string builder.search_field(:description)
656 | expect(node)
657 | .to have_css('label[for="author_description"]', text: "Description")
658 | expect(node)
659 | .to have_css('input[type="search"][name="author[description]"]')
660 | expect(node.find_field("author_description").value)
661 | .to eq @author.description
662 | end
663 | end
664 |
665 | it "should generate color_field" do
666 | form_for(@author) do |builder|
667 | node = Capybara.string builder.color_field(:favorite_color)
668 | expect(node).to have_css(
669 | 'label[for="author_favorite_color"]', text: "Favorite color"
670 | )
671 | expect(node)
672 | .to have_css('input[type="color"][name="author[favorite_color]"]')
673 | expect(node.find_field("author_favorite_color").value)
674 | .to eq @author.favorite_color
675 | end
676 | end
677 |
678 | it "should generate collection_select input" do
679 | selector = 'select[name="author[favorite_book]"] option'
680 |
681 | form_for(@author) do |builder|
682 | node = Capybara.string(
683 | builder.collection_select(:favorite_book, Book.all, :id, :title)
684 | )
685 | expect(node).to have_css(
686 | 'label[for="author_favorite_book"]',
687 | text: "Favorite book"
688 | )
689 | expect(node).to have_css('select[name="author[favorite_book]"]')
690 | expect(node)
691 | .to have_css("#{selector}[value=\"78\"]", text: "Gulliver's Travels")
692 | expect(node)
693 | .to have_css("#{selector}[value=\"133\"]", text: "Treasure Island")
694 | end
695 | end
696 |
697 | it "should generate grouped_collection_select input" do
698 | selector = 'select[name="author[favorite_book]"] optgroup'
699 |
700 | form_for(@author) do |builder|
701 | node = Capybara.string(
702 | builder.grouped_collection_select(:favorite_book, Genre.all,
703 | :books, :name, :id, :title)
704 | )
705 | expect(node).to have_css(
706 | 'label[for="author_favorite_book"]', text: "Favorite book"
707 | )
708 | expect(node).to have_css('select[name="author[favorite_book]"]')
709 | expect(node).to have_css(
710 | "#{selector}[label=\"Exploration\"] option[value=\"78\"]",
711 | text: "Gulliver's Travels"
712 | )
713 | expect(node).to have_css(
714 | "#{selector}[label=\"Pirate Exploits\"] option[value=\"133\"]",
715 | text: "Treasure Island"
716 | )
717 | end
718 | end
719 |
720 | describe "help_text" do
721 | it "should add a p element" do
722 | form_for(@author) do |builder|
723 | help_text = "Enter login"
724 | node =
725 | Capybara.string builder.text_field(:login, help_text: help_text)
726 | expect(node.find("p").text).to eq help_text
727 | end
728 | end
729 |
730 | it "should not add help_text attribute" do
731 | form_for(@author) do |builder|
732 | node =
733 | Capybara.string builder.text_field(:login, help_text: "Enter login")
734 | expect(node.find_field("author_login")["help_text"]).to be_nil
735 | end
736 | end
737 | end
738 |
739 | context "when there aren't any errors and no class option is passed" do
740 | it "should not have a class attribute" do
741 | form_for(@author) do |builder|
742 | node = Capybara.string builder.text_field(:login)
743 | expect(node).to have_css('input:not([class=""])')
744 | end
745 | end
746 | end
747 | end
748 |
749 | describe "errors generator" do
750 | it "should not display errors" do
751 | form_for(@author) do |builder|
752 | node = Capybara.string builder.text_field(:login)
753 | expect(node).to_not have_css("small.form-error")
754 | end
755 | end
756 |
757 | it "should display errors" do
758 | form_for(@author) do |builder|
759 | allow(@author).to receive(:errors).and_return(login: ["required"])
760 | node = Capybara.string builder.text_field(:login)
761 | expect(node).to have_css("small.form-error", text: "required")
762 | end
763 | end
764 |
765 | %w(file_field email_field text_field telephone_field phone_field
766 | url_field number_field range_field search_field color_field
767 | password_field).each do |field|
768 | it "should display errors on #{field} inputs" do
769 | form_for(@author) do |builder|
770 | allow(@author)
771 | .to receive(:errors).and_return(description: ["required"])
772 | node = Capybara.string builder.public_send(field, :description)
773 | expect(node)
774 | .to have_css('label.is-invalid-label[for="author_description"]')
775 | expect(node)
776 | .to have_css('input.is-invalid-input[name="author[description]"]')
777 | end
778 | end
779 | end
780 |
781 | %w(date_field datetime_field datetime_local_field month_field
782 | week_field time_field).each do |field|
783 | it "should display errors on #{field} inputs" do
784 | form_for(@author) do |builder|
785 | allow(@author)
786 | .to receive(:errors).and_return(birthdate: ["required"])
787 | node = Capybara.string builder.public_send(field, :birthdate)
788 | expect(node)
789 | .to have_css('label.is-invalid-label[for="author_birthdate"]')
790 | expect(node)
791 | .to have_css('input.is-invalid-input[name="author[birthdate]"]')
792 | end
793 | end
794 | end
795 |
796 | it "should display errors on text_area inputs" do
797 | form_for(@author) do |builder|
798 | allow(@author).to receive(:errors).and_return(description: ["required"])
799 | node = Capybara.string builder.text_area(:description)
800 | expect(node)
801 | .to have_css('label.is-invalid-label[for="author_description"]')
802 | expect(node)
803 | .to have_css('textarea.is-invalid-input[name="author[description]"]')
804 | end
805 | end
806 |
807 | it "should display errors on select inputs" do
808 | form_for(@author) do |builder|
809 | allow(@author)
810 | .to receive(:errors).and_return(favorite_book: ["required"])
811 | node = Capybara.string(
812 | builder.select(:favorite_book, [["Choice #1", :a], ["Choice #2", :b]])
813 | )
814 | expect(node)
815 | .to have_css('label.is-invalid-label[for="author_favorite_book"]')
816 | expect(node)
817 | .to have_css('select.is-invalid-input[name="author[favorite_book]"]')
818 | end
819 | end
820 |
821 | it "should display errors on date_select inputs" do
822 | form_for(@author) do |builder|
823 | allow(@author).to receive(:errors).and_return(birthdate: ["required"])
824 | node = Capybara.string builder.date_select(:birthdate)
825 | expect(node)
826 | .to have_css('label.is-invalid-label[for="author_birthdate"]')
827 | %w(1 2 3).each do |i|
828 | expect(node).to have_css(
829 | "select.is-invalid-input[name='author[birthdate(#{i}i)]']"
830 | )
831 | end
832 | end
833 | end
834 |
835 | it "should display errors on datetime_select inputs" do
836 | form_for(@author) do |builder|
837 | allow(@author).to receive(:errors).and_return(birthdate: ["required"])
838 | node = Capybara.string builder.datetime_select(:birthdate)
839 | expect(node)
840 | .to have_css('label.is-invalid-label[for="author_birthdate"]')
841 | %w(1 2 3 4 5).each do |i|
842 | expect(node).to have_css(
843 | "select.is-invalid-input[name='author[birthdate(#{i}i)]']"
844 | )
845 | end
846 | end
847 | end
848 |
849 | it "should display errors on time_zone_select inputs" do
850 | form_for(@author) do |builder|
851 | allow(@author).to receive(:errors).and_return(time_zone: ["required"])
852 | node = Capybara.string builder.time_zone_select(:time_zone)
853 | expect(node)
854 | .to have_css('label.is-invalid-label[for="author_time_zone"]')
855 | expect(node)
856 | .to have_css('select.is-invalid-input[name="author[time_zone]"]')
857 | end
858 | end
859 |
860 | it "should display errors on collection_select inputs" do
861 | form_for(@author) do |builder|
862 | allow(@author)
863 | .to receive(:errors).and_return(favorite_book: ["required"])
864 | node = Capybara.string(
865 | builder.collection_select(:favorite_book, Book.all, :id, :title)
866 | )
867 | expect(node)
868 | .to have_css('label.is-invalid-label[for="author_favorite_book"]')
869 | expect(node)
870 | .to have_css('select.is-invalid-input[name="author[favorite_book]"]')
871 | end
872 | end
873 |
874 | it "should display errors on grouped_collection_select inputs" do
875 | form_for(@author) do |builder|
876 | allow(@author)
877 | .to receive(:errors).and_return(favorite_book: ["required"])
878 | node = Capybara.string(
879 | builder.grouped_collection_select(
880 | :favorite_book,
881 | Genre.all,
882 | :books,
883 | :name,
884 | :id,
885 | :title
886 | )
887 | )
888 | expect(node)
889 | .to have_css('label.is-invalid-label[for="author_favorite_book"]')
890 | expect(node)
891 | .to have_css('select.is-invalid-input[name="author[favorite_book]"]')
892 | end
893 | end
894 |
895 | # N.B. check_box and radio_button inputs don't have the is-invalid-input
896 | # class applied
897 | it "should display HTML errors when the option is specified" do
898 | login = ['required link']
899 |
900 | form_for(@author) do |builder|
901 | allow(@author).to receive(:errors).and_return(login: login)
902 | node = Capybara.string(
903 | builder.text_field(:login, html_safe_errors: true)
904 | )
905 | expect(node).to have_link("link", href: "link_target")
906 | end
907 | end
908 |
909 | it "should not display HTML errors when the option is not specified" do
910 | login = ['required link']
911 |
912 | form_for(@author) do |builder|
913 | allow(@author).to receive(:errors).and_return(login: login)
914 | node = Capybara.string builder.text_field(:login)
915 | expect(node).to_not have_link("link", href: "link")
916 | end
917 | end
918 |
919 | it "should not display labels unless specified in the builder method" do
920 | label = "Tell me about you"
921 |
922 | form_for(@author, auto_labels: false) do |builder|
923 | node = Capybara.string(
924 | builder.text_field(:login) +
925 | builder.check_box(:active, label: true) +
926 | builder.text_field(:description, label: label)
927 | )
928 |
929 | expect(node).to_not have_css('label[for="author_login"]')
930 | expect(node).to have_css('label[for="author_active"]', text: "Active")
931 | expect(node)
932 | .to have_css('label[for="author_description"]', text: label)
933 | end
934 | end
935 |
936 | context "when class option given" do
937 | it "should add it to the error class" do
938 | form_for(@author) do |builder|
939 | allow(@author).to receive(:errors).and_return(email: ["required"])
940 | node = Capybara.string builder.text_field(:email, class: "righteous")
941 | expect(node).to have_css(
942 | 'input.righteous.is-invalid-input[name="author[email]"]'
943 | )
944 | end
945 | end
946 | end
947 |
948 | context "when invalid class option given" do
949 | it "should add it to the error class" do
950 | form_for(@author) do |builder|
951 | allow(@author).to receive(:errors).and_return(email: ["required"])
952 | node = Capybara.string builder.text_field(:email, class: :illgotten)
953 | expect(node).to have_css(
954 | 'input.illgotten.is-invalid-input[name="author[email]"]'
955 | )
956 | end
957 | end
958 | end
959 | end
960 |
961 | describe "submit button generator" do
962 | after :each do
963 | FoundationRailsHelper.reset
964 | end
965 |
966 | context "when button_class config is not set" do
967 | it "should display form button with default class" do
968 | form_for(@author) do |builder|
969 | node = Capybara.string builder.submit("Save")
970 | expect(node)
971 | .to have_css('input[type="submit"][class="success button"]')
972 | end
973 | end
974 | end
975 |
976 | context 'when button_class config is "superduper"' do
977 | before do
978 | FoundationRailsHelper.configure do |config|
979 | config.button_class = "superduper"
980 | end
981 | end
982 |
983 | it "should display form button with 'superduper' class" do
984 | form_for(@author) do |builder|
985 | node = Capybara.string builder.submit("Save")
986 | expect(node).to have_css('input[type="submit"][class="superduper"]')
987 | end
988 | end
989 | end
990 |
991 | context 'when option value is "superduper"' do
992 | it "should display form button with 'superduper' class" do
993 | form_for(@author) do |builder|
994 | node = Capybara.string builder.submit("Save", class: "superduper")
995 | expect(node).to have_css('input[type="submit"][class="superduper"]')
996 | end
997 | end
998 | end
999 | end
1000 | end
1001 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
3 | require "foundation_rails_helper"
4 | require "capybara"
5 |
6 | # turning off deprecations
7 | ActiveSupport::Deprecation.silenced = true
8 | I18n.enforce_available_locales = false
9 |
--------------------------------------------------------------------------------
/spec/support/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from:
2 | - ../../.rubocop.yml
3 |
--------------------------------------------------------------------------------
/spec/support/classes/author.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'active_model'
3 |
4 | class Author
5 | extend ActiveModel::Naming if defined?(ActiveModel::Naming)
6 | include ActiveModel::Conversion if defined?(ActiveModel::Conversion)
7 |
8 | def to_label
9 | end
10 |
11 | def persisted?
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/support/classes/book.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'active_model'
3 |
4 | class Book
5 | extend ActiveModel::Naming if defined?(ActiveModel::Naming)
6 | include ActiveModel::Conversion if defined?(ActiveModel::Conversion)
7 |
8 | def to_label
9 | end
10 |
11 | def persisted?
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/support/classes/genre.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'active_model'
3 |
4 | class Genre
5 | extend ActiveModel::Naming if defined?(ActiveModel::Naming)
6 | include ActiveModel::Conversion if defined?(ActiveModel::Conversion)
7 |
8 | def to_label
9 | end
10 |
11 | def persisted?
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/support/mock_rails.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'bundler/setup'
3 | require 'action_view'
4 | require 'action_controller'
5 | require 'active_model'
6 | require 'active_support/core_ext'
7 |
8 | # Thanks to Justin French for formtastic spec
9 | module FoundationRailsSpecHelper
10 | include ActionView::Helpers::FormHelper
11 | include ActionView::Helpers::FormOptionsHelper
12 | include ActionView::Helpers::DateHelper
13 | if defined?(ActionController::PolymorphicRoutes)
14 | include ActionController::PolymorphicRoutes
15 | end
16 | include ActionDispatch::Routing::PolymorphicRoutes
17 | include AbstractController::UrlFor if defined?(AbstractController::UrlFor)
18 | # to use dom_class in Rails 4 tests
19 | # in Rails 5, RecordIdentifier is already required by FormHelper module
20 | include ActionView::RecordIdentifier
21 |
22 | def active_model_validator(kind, attributes, options = {})
23 | validator = mock(
24 | "ActiveModel::Validations::#{kind.to_s.titlecase}Validator",
25 | attributes: attributes,
26 | options: options
27 | )
28 | allow(validator).to receive(:kind).and_return(kind)
29 | validator
30 | end
31 |
32 | def active_model_presence_validator(attributes, options = {})
33 | active_model_validator(:presence, attributes, options)
34 | end
35 |
36 | def active_model_length_validator(attributes, options = {})
37 | active_model_validator(:length, attributes, options)
38 | end
39 |
40 | def active_model_inclusion_validator(attributes, options = {})
41 | active_model_validator(:inclusion, attributes, options)
42 | end
43 |
44 | def active_model_numericality_validator(attributes, options = {})
45 | active_model_validator(:numericality, attributes, options)
46 | end
47 |
48 | def mock_everything
49 | mock_author
50 | mock_books
51 | mock_genres
52 | end
53 |
54 | # Resource-oriented styles like form_for(@post) will expect a path method
55 | # for the object, so we're defining some here.
56 | # the argument is required for Rails 4 tests
57 | def authors_path(*_args)
58 | '/authors'
59 | end
60 |
61 | def _routes
62 | double('_routes', polymorphic_mappings: {})
63 | end
64 |
65 | def self.included(base)
66 | base.class_eval do
67 | attr_accessor :output_buffer
68 |
69 | def protect_against_forgery?
70 | false
71 | end
72 | end
73 | end
74 |
75 | private
76 |
77 | def mock_author
78 | @author = ::Author.new
79 | { login: 'fred_smith', email: 'fred@foo.com', url: 'http://example.com',
80 | some_number: '42', phone: '317 456 2564', active: true,
81 | description: 'bla bla bla', birthdate: DateTime.parse('1969-06-18 20:30'),
82 | forty_two: DateTime.parse('1969-06-18 20:30') + 42.years,
83 | time_zone: 'Perth', publish_date: Date.new(2000, 1, 1),
84 | favorite_color: '#424242', favorite_book: nil }.each do |m, v|
85 | allow(@author).to receive(m).and_return(v)
86 | end
87 | end
88 |
89 | def mock_books
90 | mock_book_0
91 | mock_book_1
92 | allow(::Book).to receive(:all).and_return([@book_0, @book_1])
93 | end
94 |
95 | def mock_book_0
96 | @book_0 = ::Book.new
97 | allow(@book_0).to receive(:id).and_return('78')
98 | allow(@book_0).to receive(:title).and_return("Gulliver's Travels")
99 | end
100 |
101 | def mock_book_1
102 | @book_1 = ::Book.new
103 | allow(@book_1).to receive(:id).and_return('133')
104 | allow(@book_1).to receive(:title).and_return('Treasure Island')
105 | end
106 |
107 | def mock_genres
108 | mock_genre_0
109 | mock_genre_1
110 | allow(::Genre).to receive(:all).and_return([@genre_0, @genre_1])
111 | end
112 |
113 | def mock_genre_0
114 | @genre_0 = ::Genre.new
115 | allow(@genre_0).to receive(:name).and_return('Exploration')
116 | allow(@genre_0).to receive(:books).and_return([@book_0])
117 | end
118 |
119 | def mock_genre_1
120 | @genre_1 = ::Genre.new
121 | allow(@genre_1).to receive(:name).and_return('Pirate Exploits')
122 | allow(@genre_1).to receive(:books).and_return([@book_1])
123 | end
124 | end
125 |
--------------------------------------------------------------------------------