├── .rspec
├── spec
├── support
│ ├── .rubocop.yml
│ └── mock_rails.rb
├── .rubocop.yml
├── spec_helper.rb
└── foundation_rails_helper
│ ├── configuration_spec.rb
│ ├── flash_helper_spec.rb
│ └── form_builder_spec.rb
├── lib
├── foundation_rails_helper
│ ├── version.rb
│ ├── action_view_extension.rb
│ ├── configuration.rb
│ ├── flash_helper.rb
│ └── form_builder.rb
├── railtie.rb
└── foundation_rails_helper.rb
├── Gemfile
├── Rakefile
├── .gitignore
├── .rubocop.yml
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── foundation_rails_helper.gemspec
├── CHANGELOG.md
├── .rubocop_todo.yml
└── README.md
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --backtrace
3 |
--------------------------------------------------------------------------------
/spec/support/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from:
2 | - ../../.rubocop.yml
3 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/version.rb:
--------------------------------------------------------------------------------
1 | module FoundationRailsHelper
2 | VERSION = '2.0.0'.freeze
3 | end
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in foundation_rails_helper.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/spec/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from:
2 | - ../.rubocop.yml
3 |
4 | StringLiterals:
5 | EnforcedStyle: double_quotes
6 | Exclude:
7 | - './spec_helper.rb'
8 |
--------------------------------------------------------------------------------
/lib/railtie.rb:
--------------------------------------------------------------------------------
1 | module Railtie
2 | class Railtie < Rails::Railtie
3 | # initializer 'setup foundation form builder' do
4 | #
5 | # end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/action_view_extension.rb:
--------------------------------------------------------------------------------
1 | ActionView::Base.default_form_builder = FoundationRailsHelper::FormBuilder
2 | ActionView::Base.field_error_proc = proc do |html_tag, _instance_tag|
3 | html_tag
4 | end
5 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | require 'bundler/gem_tasks'
3 | require 'rspec/core/rake_task'
4 | require 'rubocop/rake_task'
5 |
6 | RSpec::Core::RakeTask.new(:spec)
7 | RuboCop::RakeTask.new(:rubocop)
8 | task default: [:spec, :rubocop]
9 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
2 | require "foundation_rails_helper"
3 | require "capybara"
4 |
5 | # turning off deprecations
6 | ActiveSupport::Deprecation.silenced = true
7 | I18n.enforce_available_locales = false
8 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper.rb:
--------------------------------------------------------------------------------
1 | require 'foundation_rails_helper/version'
2 | require 'foundation_rails_helper/configuration'
3 | require 'foundation_rails_helper/form_builder'
4 | require 'foundation_rails_helper/flash_helper'
5 | require 'foundation_rails_helper/action_view_extension'
6 | ActiveSupport.on_load(:action_view) do
7 | include FoundationRailsHelper::FlashHelper
8 | end
9 |
--------------------------------------------------------------------------------
/.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 | # A quick fix for not having magic comments at the top of each file
13 | # # frozen_string_literal: true
14 | # https://github.com/bbatsov/rubocop/issues/3284
15 | FrozenStringLiteralComment:
16 | Enabled: false
17 |
18 | Documentation:
19 | Enabled: false
20 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | branches:
3 | only:
4 | - master
5 | rvm:
6 | - 2.1.9
7 | - 2.2.5
8 | - 2.3.1
9 | - 2.4.0-preview1
10 | env:
11 | - "RAILS_VERSION=4.1.0"
12 | - "RAILS_VERSION=4.2.0"
13 | - "RAILS_VERSION=5.0.0"
14 | matrix:
15 | exclude:
16 | - rvm: 2.1.9
17 | env: "RAILS_VERSION=5.0.0"
18 | - rvm: 2.4.0-preview1
19 | env: "RAILS_VERSION=4.1.0"
20 | - rvm: 2.4.0-preview1
21 | env: "RAILS_VERSION=4.2.0"
22 | sudo: false
23 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/configuration.rb:
--------------------------------------------------------------------------------
1 | module FoundationRailsHelper
2 | class << self
3 | attr_writer :configuration
4 | end
5 |
6 | def self.configuration
7 | @configuration ||= Configuration.new
8 | end
9 |
10 | def self.reset
11 | @configuration = Configuration.new
12 | end
13 |
14 | def self.configure
15 | yield(configuration)
16 | end
17 |
18 | class Configuration
19 | attr_accessor :button_class
20 | attr_accessor :ignored_flash_keys
21 |
22 | def initialize
23 | @button_class = 'success button'
24 | @ignored_flash_keys = []
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/foundation_rails_helper.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require File.expand_path('../lib/foundation_rails_helper/version', __FILE__)
3 |
4 | Gem::Specification.new do |gem|
5 | gem.authors = ['Sebastien Gruhier']
6 | gem.email = ['sebastien.gruhier@xilinus.com']
7 | gem.description = 'Rails for zurb foundation CSS framework. Form builder, flash message, ...'
8 | gem.summary = 'Rails helpers for zurb foundation CSS framework'
9 | gem.homepage = 'http://github.com/sgruhier/foundation_rails_helper'
10 |
11 | gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
12 | gem.files = `git ls-files`.split("\n")
13 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14 | gem.name = 'foundation_rails_helper'
15 | gem.require_paths = ['lib']
16 | gem.version = FoundationRailsHelper::VERSION
17 | gem.license = 'MIT'
18 |
19 | # Allow different versions of the rails gems to be specified, for testing:
20 | rails_version = ENV['RAILS_VERSION'] || 'default'
21 |
22 | rails = case rails_version
23 | when 'default'
24 | '>= 4.1'
25 | else
26 | "~> #{rails_version}"
27 | end
28 |
29 | gem.add_dependency 'railties', rails
30 | gem.add_dependency 'actionpack', rails
31 | gem.add_dependency 'activemodel', rails
32 | gem.add_dependency 'activesupport', rails
33 | gem.add_dependency 'tzinfo', '~> 1.2', '>= 1.2.2'
34 |
35 | gem.add_development_dependency 'rspec-rails', '>= 3.1'
36 | gem.add_development_dependency 'mime-types', '~> 2'
37 | gem.add_development_dependency 'capybara', '~> 2.7'
38 | gem.add_development_dependency 'rubocop', '> 0.41'
39 | end
40 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Version 2.0
2 | This will be used for Foundation 5 support
3 |
4 | ### Breaking changes:
5 | * Dropped support for Ruby 1.9.3
6 | * display_flash_messages now requires the key_matching hash to be prefixed with the keyword argument :key_matching
7 |
8 | ### Features:
9 | * Add Rubocop code style linting
10 |
11 | ## Version 1.2.2
12 | * Fix Rails 5 deprecation warnings about alias_method_chain
13 | * Allow Capybara to be upgraded beyond 2.6.x
14 |
15 | ## Version 1.2.1
16 | * Lock Capybara at 2.6.x
17 |
18 | ## Version 1.2.0
19 | * Allow Rails 5 to be used
20 |
21 | ## Version 1.1.0
22 | * Form Helper: [Prefix and
23 | 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)
24 | * 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)
25 | * FIX: Hints are added as span elements (PR #96 thanks collimarco)
26 | * FIX: Labels and fields don't have empty class attributes or repeated error classes
27 | (thanks collimarco)
28 | * FIX: Radio buttons don't have the `label="false"` on them when `label:
29 | false` is set (PR #107 thanks frenkel)
30 |
31 | ## Version 1.0.0
32 |
33 | * Released Feb 03rd 2015
34 | * Added configuration option to set button class in an initializer
35 | * Updated to be compatible with Foundation 5.2.2
36 | * Bugfixes
37 |
38 | ## Version 0.5.0
39 |
40 | * Released Oct 10th 2014
41 | * Bugfixes
42 |
43 | ## Version 0.4
44 |
45 | * Not released
46 | * Compatibility with Rails 4
47 | * Bugfixes
48 |
49 | ## Version 0.3
50 |
51 | * Not released
52 | * Mostly bugfixes
53 |
54 | ## Version 0.2.1
55 |
56 | * First production release (Jan 14th, 2012)
57 |
58 | ## Version 0.1.rc
59 |
60 | * initial release candidate (Jan 14th, 2012)
61 |
--------------------------------------------------------------------------------
/.rubocop_todo.yml:
--------------------------------------------------------------------------------
1 | # This configuration was generated by
2 | # `rubocop --auto-gen-config`
3 | # on 2016-09-14 16:08:41 -0700 using RuboCop version 0.42.0.
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: 3
10 | Lint/NestedMethodDefinition:
11 | Exclude:
12 | - 'spec/support/mock_rails.rb'
13 |
14 | # Offense count: 3
15 | Metrics/AbcSize:
16 | Max: 179
17 |
18 | # Offense count: 1
19 | # Configuration parameters: CountComments.
20 | Metrics/ClassLength:
21 | Max: 188
22 |
23 | # Offense count: 1
24 | Metrics/CyclomaticComplexity:
25 | Max: 7
26 |
27 | # Offense count: 157
28 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
29 | # URISchemes: http, https
30 | Metrics/LineLength:
31 | Max: 163
32 |
33 | # Offense count: 5
34 | # Configuration parameters: CountComments.
35 | Metrics/MethodLength:
36 | Max: 55
37 |
38 | # Offense count: 1
39 | # Configuration parameters: CountComments.
40 | Metrics/ModuleLength:
41 | Max: 102
42 |
43 | # Offense count: 2
44 | # Configuration parameters: CountKeywordArgs.
45 | Metrics/ParameterLists:
46 | Max: 8
47 |
48 | # Offense count: 1
49 | Metrics/PerceivedComplexity:
50 | Max: 8
51 |
52 | # Offense count: 3
53 | # Configuration parameters: EnforcedStyle, SupportedStyles.
54 | # SupportedStyles: nested, compact
55 | Style/ClassAndModuleChildren:
56 | Exclude:
57 | - 'spec/support/mock_rails.rb'
58 |
59 | # Offense count: 1
60 | # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
61 | # NamePrefix: is_, has_, have_
62 | # NamePrefixBlacklist: is_, has_, have_
63 | # NameWhitelist: is_a?
64 | Style/PredicateName:
65 | Exclude:
66 | - 'spec/**/*'
67 | - 'lib/foundation_rails_helper/form_builder.rb'
68 |
--------------------------------------------------------------------------------
/spec/foundation_rails_helper/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe FoundationRailsHelper do
4 | describe FoundationRailsHelper::Configuration do
5 | describe "#button_class" do
6 | it "default value is 'success button'" do
7 | config = FoundationRailsHelper::Configuration.new
8 | expect(config.button_class).to eq("success button")
9 | end
10 | end
11 |
12 | describe "#button_class=" do
13 | it "can set value" do
14 | config = FoundationRailsHelper::Configuration.new
15 | config.button_class = "new-class"
16 | expect(config.button_class).to eq("new-class")
17 | end
18 | end
19 |
20 | describe "#ignored_flash_keys" do
21 | it "defaults to empty" do
22 | config = FoundationRailsHelper::Configuration.new
23 | expect(config.ignored_flash_keys).to eq([])
24 | end
25 | end
26 |
27 | describe "#ignored_flash_keys=" do
28 | it "can set the value" do
29 | config = FoundationRailsHelper::Configuration.new
30 | config.ignored_flash_keys = [:foo]
31 | expect(config.ignored_flash_keys).to eq([:foo])
32 | end
33 | end
34 |
35 | describe ".reset" do
36 | it "resets the configured button class" do
37 | FoundationRailsHelper.configure do |config|
38 | config.button_class = "new-class"
39 | end
40 |
41 | FoundationRailsHelper.reset
42 |
43 | config = FoundationRailsHelper.configuration
44 | expect(config.button_class).to eq("success button")
45 | end
46 |
47 | it "resets the configured ignored flash keys" do
48 | FoundationRailsHelper.configure do |config|
49 | config.ignored_flash_keys = [:new_key]
50 | end
51 |
52 | FoundationRailsHelper.reset
53 |
54 | config = FoundationRailsHelper.configuration
55 | expect(config.ignored_flash_keys).to eq([])
56 | end
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/flash_helper.rb:
--------------------------------------------------------------------------------
1 | require 'action_view/helpers'
2 |
3 | module FoundationRailsHelper
4 | module FlashHelper
5 | #
6 | # This is an alert box.
7 | #
10 | #
11 | DEFAULT_KEY_MATCHING = {
12 | alert: :alert,
13 | notice: :success,
14 | info: :info,
15 | secondary: :secondary,
16 | success: :success,
17 | error: :alert,
18 | warning: :warning,
19 | primary: :primary
20 | }.freeze
21 |
22 | # Displays the flash messages found in ActionDispatch's +flash+ hash using Foundation's
23 | # +callout+ component.
24 | #
25 | # Parameters:
26 | # * +closable+ - A boolean to determine whether the displayed flash messages should
27 | # be closable by the user. Defaults to true.
28 | # * +key_matching+ - A Hash of key/value pairs mapping flash keys to the
29 | # corresponding class to use for the callout box.
30 | def display_flash_messages(closable: true, key_matching: {})
31 | key_matching = DEFAULT_KEY_MATCHING.merge(key_matching)
32 | key_matching.default = :primary
33 |
34 | capture do
35 | flash.each do |key, value|
36 | next if FoundationRailsHelper.configuration.ignored_flash_keys.include? key.to_sym
37 | alert_class = key_matching[key.to_sym]
38 | concat alert_box(value, alert_class, closable)
39 | end
40 | end
41 | end
42 |
43 | private
44 |
45 | def alert_box(value, alert_class, closable)
46 | options = { class: "flash callout #{alert_class}" }
47 | options[:data] = { closable: '' } if closable
48 | content_tag(:div, options) do
49 | concat value
50 | concat close_link if closable
51 | end
52 | end
53 |
54 | def close_link
55 | button_tag(
56 | class: 'close-button',
57 | type: 'button',
58 | data: { close: '' },
59 | aria: { label: 'Dismiss alert' }
60 | ) do
61 | content_tag(:span, '×'.html_safe, aria: { hidden: true })
62 | end
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/foundation_rails_helper/flash_helper_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require "spec_helper"
4 |
5 | describe FoundationRailsHelper::FlashHelper do
6 | include ActionView::Context if defined?(ActionView::Context)
7 | include ActionView::Helpers::UrlHelper
8 | include ActionView::Helpers::TagHelper
9 | include ActionView::Helpers::TextHelper
10 | include ActionView::Helpers::FormTagHelper
11 | include FoundationRailsHelper::FlashHelper
12 |
13 | FoundationRailsHelper::FlashHelper::DEFAULT_KEY_MATCHING.each do |message_type, foundation_type|
14 | it "displays flash message with #{foundation_type} class for #{message_type} message" do
15 | allow(self).to receive(:flash).and_return(message_type.to_s => "Flash message")
16 | node = Capybara.string display_flash_messages
17 | expect(node)
18 | .to have_css("div.flash.callout.#{foundation_type}", text: "Flash message")
19 | .and have_css("[data-close]", text: "×")
20 | end
21 | end
22 |
23 | it "handles symbol keys" do
24 | allow(self).to receive(:flash).and_return(success: "Flash message")
25 | node = Capybara.string display_flash_messages
26 | expect(node).to have_css("div.callout.success", text: "Flash message")
27 | end
28 |
29 | it "handles string keys" do
30 | allow(self).to receive(:flash).and_return("success" => "Flash message")
31 | node = Capybara.string display_flash_messages
32 | expect(node).to have_css("div.callout.success", text: "Flash message")
33 | end
34 |
35 | it "displays multiple flash messages" do
36 | allow(self).to receive(:flash).and_return("success" => "Yay it worked", "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 = Capybara.string display_flash_messages(key_matching: { notice: :alert })
46 | expect(node).to have_css("div.callout.alert", text: "Flash message")
47 | end
48 |
49 | it "displays flash message with custom key matching" do
50 | allow(self).to receive(:flash).and_return("custom_type" => "Flash message")
51 | node = Capybara.string display_flash_messages(key_matching: { custom_type: :custom_class })
52 | expect(node).to have_css("div.callout.custom_class", text: "Flash message")
53 | end
54 |
55 | it "displays flash message with standard class if key doesn't match" do
56 | allow(self).to receive(:flash).and_return("custom_type" => "Flash message")
57 | node = Capybara.string display_flash_messages
58 | expect(node).to have_css("div.callout.primary", text: "Flash message")
59 | end
60 |
61 | context "when the flash hash contains devise internal data" do
62 | before do
63 | FoundationRailsHelper.configure do |config|
64 | config.ignored_flash_keys += [:timedout]
65 | end
66 | end
67 |
68 | it "doesn't raise an error (e.g. NoMethodError)" do
69 | allow(self).to receive(:flash).and_return("timedout" => true)
70 | expect { Capybara.string display_flash_messages }.not_to raise_error
71 | end
72 |
73 | it "doesn't display an alert for that data" do
74 | allow(self).to receive(:flash).and_return("timedout" => true)
75 | expect(display_flash_messages).to be_nil
76 |
77 | # Ideally we'd create a node using Capybara.string, as in the other examples
78 | # and set the following expectation:
79 | # expect(node).to_not have_css("div.callout")
80 | # but Capybara.string doesn't behave nicely with nil input:
81 | # the input gets assigned to the @native instance variable,
82 | # which is used by the css matcher, so we get the following error:
83 | # undefined method `css' for nil:NilClass
84 | end
85 | end
86 |
87 | context "with (closable: false) option" do
88 | it "doesn't display the close button" do
89 | allow(self).to receive(:flash).and_return(success: "Flash message")
90 | node = Capybara.string display_flash_messages(closable: false)
91 | expect(node)
92 | .to have_css("div.flash.callout.success", text: "Flash message")
93 | .and have_no_css("[data-close]", text: "×")
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/spec/support/mock_rails.rb:
--------------------------------------------------------------------------------
1 | require 'bundler/setup'
2 | require 'active_support'
3 | require 'action_pack'
4 | require 'action_view'
5 | require 'action_controller'
6 | require 'action_dispatch'
7 | require 'active_model'
8 | require 'active_support/core_ext'
9 |
10 | # Thanks to Justin French for formtastic spec
11 | module FoundationRailsSpecHelper
12 | include ActionPack
13 | include ActionView::Context if defined?(ActionView::Context)
14 | include ActionView::RecordIdentifier
15 | include ActionView::Helpers::FormHelper
16 | include ActionView::Helpers::FormTagHelper
17 | include ActionView::Helpers::FormOptionsHelper
18 | include ActionView::Helpers::UrlHelper
19 | include ActionView::Helpers::TagHelper
20 | include ActionView::Helpers::TextHelper
21 | include ActionView::Helpers::ActiveRecordHelper if defined?(ActionView::Helpers::ActiveRecordHelper)
22 | include ActionView::Helpers::ActiveModelHelper if defined?(ActionView::Helpers::ActiveModelHelper)
23 | include ActionView::Helpers::DateHelper
24 | include ActionView::Helpers::CaptureHelper
25 | include ActionView::Helpers::AssetTagHelper
26 | include ActiveSupport
27 | include ActionController::PolymorphicRoutes if defined?(ActionController::PolymorphicRoutes)
28 | include ActionDispatch::Routing::UrlFor
29 |
30 | def active_model_validator(kind, attributes, options = {})
31 | validator = mock("ActiveModel::Validations::#{kind.to_s.titlecase}Validator", attributes: attributes, options: options)
32 | allow(validator).to receive(:kind).and_return(kind)
33 | validator
34 | end
35 |
36 | def active_model_presence_validator(attributes, options = {})
37 | active_model_validator(:presence, attributes, options)
38 | end
39 |
40 | def active_model_length_validator(attributes, options = {})
41 | active_model_validator(:length, attributes, options)
42 | end
43 |
44 | def active_model_inclusion_validator(attributes, options = {})
45 | active_model_validator(:inclusion, attributes, options)
46 | end
47 |
48 | def active_model_numericality_validator(attributes, options = {})
49 | active_model_validator(:numericality, attributes, options)
50 | end
51 |
52 | class ::Author
53 | extend ActiveModel::Naming if defined?(ActiveModel::Naming)
54 | include ActiveModel::Conversion if defined?(ActiveModel::Conversion)
55 |
56 | def to_label
57 | end
58 |
59 | def persisted?
60 | end
61 | end
62 |
63 | class ::Book
64 | extend ActiveModel::Naming if defined?(ActiveModel::Naming)
65 | include ActiveModel::Conversion if defined?(ActiveModel::Conversion)
66 |
67 | def to_label
68 | end
69 |
70 | def persisted?
71 | end
72 | end
73 |
74 | class ::Genre
75 | extend ActiveModel::Naming if defined?(ActiveModel::Naming)
76 | include ActiveModel::Conversion if defined?(ActiveModel::Conversion)
77 |
78 | def to_label
79 | end
80 |
81 | def persisted?
82 | end
83 | end
84 |
85 | def mock_everything
86 | # Resource-oriented styles like form_for(@post) will expect a path method for the object,
87 | # so we're defining some here.
88 | def author_path(*_args)
89 | '/authors/1'
90 | end
91 |
92 | def authors_path(*_args)
93 | '/authors'
94 | end
95 |
96 | def new_author_path(*_args)
97 | '/authors/new'
98 | end
99 |
100 | @author = ::Author.new
101 | allow(@author).to receive(:class).and_return(::Author)
102 | allow(@author).to receive(:to_label).and_return('Fred Smith')
103 | allow(@author).to receive(:login).and_return('fred_smith')
104 | allow(@author).to receive(:email).and_return('fred@foo.com')
105 | allow(@author).to receive(:url).and_return('http://example.com')
106 | allow(@author).to receive(:some_number).and_return('42')
107 | allow(@author).to receive(:phone).and_return('317 456 2564')
108 | allow(@author).to receive(:password).and_return('secret')
109 | allow(@author).to receive(:active).and_return(true)
110 | allow(@author).to receive(:description).and_return('bla bla bla')
111 | allow(@author).to receive(:avatar).and_return('avatar.png')
112 | allow(@author).to receive(:birthdate).and_return(DateTime.parse('1969-06-18 20:30'))
113 | allow(@author).to receive(:id).and_return(37)
114 | allow(@author).to receive(:new_record?).and_return(false)
115 | allow(@author).to receive(:errors).and_return(double('errors', :[] => nil))
116 | allow(@author).to receive(:to_key).and_return(nil)
117 | allow(@author).to receive(:persisted?).and_return(nil)
118 | allow(@author).to receive(:time_zone).and_return('Perth')
119 | allow(@author).to receive(:publish_date).and_return(Date.new(2000, 1, 1))
120 | allow(@author).to receive(:forty_two).and_return(@author.birthdate + 42.years)
121 | allow(@author).to receive(:favorite_color).and_return('#424242')
122 | allow(@author).to receive(:favorite_book).and_return(nil)
123 |
124 | @book_0 = ::Book.new
125 | allow(@book_0).to receive(:id).and_return('78')
126 | allow(@book_0).to receive(:title).and_return("Gulliver's Travels")
127 | @book_1 = ::Book.new
128 | allow(@book_1).to receive(:id).and_return('133')
129 | allow(@book_1).to receive(:title).and_return('Treasure Island')
130 | @genre_0 = ::Genre.new
131 | allow(@genre_0).to receive(:name).and_return('Exploration')
132 | allow(@genre_0).to receive(:books).and_return([@book_0])
133 | @genre_1 = ::Genre.new
134 | allow(@genre_1).to receive(:name).and_return('Pirate Exploits')
135 | allow(@genre_1).to receive(:books).and_return([@book_1])
136 |
137 | allow(::Author).to receive(:scoped).and_return(::Author)
138 | allow(::Author).to receive(:find).and_return([@author])
139 | allow(::Author).to receive(:all).and_return([@author])
140 | allow(::Author).to receive(:where).and_return([@author])
141 | allow(::Author).to receive(:human_attribute_name) { |column_name| column_name.to_s.humanize }
142 | allow(::Author).to receive(:human_name).and_return('::Author')
143 | allow(::Author).to receive(:content_columns).and_return([double('column', name: 'login'), double('column', name: 'created_at')])
144 | allow(::Author).to receive(:to_key).and_return(nil)
145 | allow(::Author).to receive(:persisted?).and_return(nil)
146 |
147 | allow(::Book).to receive(:all).and_return([@book_0, @book_1])
148 | allow(::Genre).to receive(:all).and_return([@genre_0, @genre_1])
149 | end
150 |
151 | def self.included(base)
152 | base.class_eval do
153 | attr_accessor :output_buffer
154 |
155 | def protect_against_forgery?
156 | false
157 | end
158 | end
159 | end
160 | end
161 |
--------------------------------------------------------------------------------
/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.
4 |
5 | * [Zurb Foundation](https://github.com/zurb/foundation)
6 | * [Zurb Foundation Rails](https://github.com/zurb/foundation-rails)
7 |
8 | So far it includes:
9 |
10 | * 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.
11 |
12 | * A `display_flash_messages` helper method that uses Zurb Foundation Callout UI.
13 |
14 | #### Compatibility
15 |
16 | * Only Rails 4.1/4.2/5, and Foundation 6 are fully supported
17 | * 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
18 | * 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.
19 | * We test against ruby versions 2.1 and up. This gem may still work fine on 1.9.3, but your mileage may vary
20 |
21 |
22 | ## Screenshots
23 |
24 | ### Forms
25 | A classic devise sign up view will look like this:
26 |
27 | ```erb
28 | <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
29 | <%= f.email_field :email %>
30 | <%= f.password_field :password %>
31 | <%= f.password_field :password_confirmation %>
32 |
33 | <%= f.submit %>
34 | <% end %>
35 | ```
36 |
37 |
38 |
39 |
40 |
Form
41 |
Form with errors
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | ### Flash messages
57 |
58 | 
59 |
60 | ## Installation
61 |
62 | Add this line to your application's Gemfile:
63 |
64 | ```ruby
65 | gem 'foundation-rails' # required
66 | gem 'foundation_rails_helper', '~> 3.0.0'
67 | ```
68 |
69 | And then execute:
70 |
71 | ```bash
72 | $ bundle
73 | ```
74 |
75 | ### Flash Messages
76 |
77 | To use the built in flash helper, add `<%= display_flash_messages %>` to your layout file (eg. *app/views/layouts/application.html.erb*).
78 |
79 | ## Usage
80 |
81 | ### form_for
82 |
83 | Form_for wraps the standard rails form_for helper.
84 |
85 | ```erb
86 | <%= form_for @user do |f| %>
87 | ...
88 | <% end %>
89 | ```
90 |
91 | generates:
92 |
93 | ```html
94 |
97 | ```
98 |
99 | ### text_field and Field Helpers
100 |
101 | Field helpers add a label element and an input of the proper type.
102 |
103 | ```ruby
104 | f.text_field :name
105 | ```
106 |
107 | generates:
108 |
109 | ```html
110 |
111 |
112 | ```
113 |
114 | Preventing the generation of labels can be accomplished two ways. To disable on a form element:
115 | ```ruby
116 | f.text_field :name, label: false
117 | ```
118 | For all form elements, add the option: `auto_labels: false` to the form helper.
119 |
120 | Change the label text and add a class on the label:
121 |
122 | ```ruby
123 | f.text_field :name, label: 'Nombre', label_options: { class: 'large' }
124 | ```
125 |
126 | If the help_text option is specified
127 |
128 | ```ruby
129 | f.text_field :name, help_text: "I'm a text field"
130 | ```
131 |
132 | an additional p element will be added after the input element:
133 |
134 | ```html
135 |
I'm a text field
136 | ```
137 |
138 | ### Submit Button
139 |
140 | The 'submit' helper wraps the rails helper and sets the class attribute to "success button" by default.
141 |
142 | ```ruby
143 | f.submit
144 | ```
145 |
146 | generates:
147 |
148 | ```html
149 |
150 | ```
151 |
152 | Specify the class option to override the default classes.
153 |
154 | ### Errors
155 |
156 | On error,
157 |
158 | ```ruby
159 | f.email_field :email
160 | ```
161 |
162 | generates:
163 |
164 | ```html
165 |
166 |
167 | can't be blank
168 | ```
169 |
170 | The class attribute of the 'small' element will mirror the class attribute of the 'input' element.
171 |
172 | 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.
173 |
174 | ### Prefix and Postfix
175 | Simple prefix and postfix span elements can be added beside inputs.
176 | ```ruby
177 | f.text_field :name, prefix { value: 'foo', small: 2, large: 3 }
178 | ```
179 | generates
180 | ```html
181 |
182 |
183 | foo
184 |
185 |
186 |
187 |
188 |
189 | ```
190 |
191 |
192 | ## Configuration
193 | Add an initializer file to your Rails app: *config/initializers/foundation_rails_helper.rb*
194 | containing the following block:
195 |
196 | ```ruby
197 | FoundationRailsHelper.configure do |config|
198 | # your options here
199 | end
200 | ```
201 |
202 | Currently supported options:
203 |
204 | ### Submit Button Class
205 | 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**:
206 | ```ruby
207 | # Default: 'success button'
208 | config.button_class = 'large hollow secondary button'
209 | ```
210 |
211 | Please note, the button class can still be overridden by an options hash.
212 |
213 | ### Ignored Flash Keys
214 | The flash helper assumes all flash entries are user-viewable messages.
215 | To exclude flash entries which are used for storing state
216 | (e.g. [Devise's `:timedout` flash](https://github.com/plataformatec/devise/issues/1777))
217 | you can specify a blacklist of keys to ignore with the **ignored_flash_keys** config option:
218 | ```ruby
219 | # Default: []
220 | config.ignored_flash_keys = [:timedout]
221 | ```
222 |
223 | ## Contributing
224 |
225 | See the [CONTRIBUTING](CONTRIBUTING.md) file.
226 |
227 | ## Copyright
228 |
229 | Sébastien Gruhier (http://xilinus.com, http://v2.maptimize.com) - MIT LICENSE
230 |
--------------------------------------------------------------------------------
/lib/foundation_rails_helper/form_builder.rb:
--------------------------------------------------------------------------------
1 | require 'action_view/helpers'
2 |
3 | module FoundationRailsHelper
4 | class FormBuilder < ActionView::Helpers::FormBuilder
5 | include ActionView::Helpers::TagHelper
6 | %w(file_field email_field text_field text_area telephone_field phone_field
7 | url_field number_field date_field datetime_field datetime_local_field
8 | month_field week_field time_field range_field search_field color_field)
9 | .each do |method_name|
10 | define_method(method_name) do |*args|
11 | attribute = args[0]
12 | options = args[1] || {}
13 | field(attribute, options) do |opts|
14 | super(attribute, opts)
15 | end
16 | end
17 | end
18 |
19 | def label(attribute, text = nil, options = {})
20 | if has_error?(attribute)
21 | options[:class] ||= ''
22 | options[:class] += ' is-invalid-label'
23 | end
24 |
25 | super(attribute, (text || '').html_safe, options)
26 | end
27 |
28 | def check_box(attribute, options = {}, checked_value = '1', unchecked_value = '0')
29 | custom_label(attribute, options[:label], options[:label_options]) do
30 | options.delete(:label)
31 | options.delete(:label_options)
32 | super(attribute, options, checked_value, unchecked_value)
33 | end + error_and_help_text(attribute, options)
34 | end
35 |
36 | def radio_button(attribute, tag_value, options = {})
37 | options[:label_options] ||= {}
38 | label_options = options.delete(:label_options).merge!(value: tag_value)
39 | label_text = options.delete(:label)
40 | l = label(attribute, label_text, label_options) unless label_text == false
41 | r = @template.radio_button(@object_name, attribute, tag_value,
42 | objectify_options(options))
43 |
44 | "#{r}#{l}".html_safe
45 | end
46 |
47 | def password_field(attribute, options = {})
48 | field attribute, options do |opts|
49 | super(attribute, opts.merge(autocomplete: :off))
50 | end
51 | end
52 |
53 | def datetime_select(attribute, options = {}, html_options = {})
54 | field attribute, options, html_options do |html_opts|
55 | super(attribute, options, html_opts.merge(autocomplete: :off))
56 | end
57 | end
58 |
59 | def date_select(attribute, options = {}, html_options = {})
60 | field attribute, options, html_options do |html_opts|
61 | super(attribute, options, html_opts.merge(autocomplete: :off))
62 | end
63 | end
64 |
65 | def time_zone_select(attribute, priorities = nil, options = {}, html_options = {})
66 | field attribute, options, html_options do |html_opts|
67 | super(attribute, priorities, options,
68 | html_opts.merge(autocomplete: :off))
69 | end
70 | end
71 |
72 | def select(attribute, choices, options = {}, html_options = {})
73 | field attribute, options, html_options do |html_opts|
74 | html_options[:autocomplete] ||= :off
75 | super(attribute, choices, options, html_opts)
76 | end
77 | end
78 |
79 | def collection_select(attribute, collection, value_method, text_method, options = {}, html_options = {})
80 | field attribute, options, html_options do |html_opts|
81 | html_options[:autocomplete] ||= :off
82 | super(attribute, collection, value_method, text_method, options,
83 | html_opts)
84 | end
85 | end
86 |
87 | def grouped_collection_select(attribute, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
88 | field attribute, options, html_options do |html_opts|
89 | html_options[:autocomplete] ||= :off
90 | super(attribute, collection, group_method, group_label_method,
91 | option_key_method, option_value_method, options, html_opts)
92 | end
93 | end
94 |
95 | def autocomplete(attribute, url, options = {})
96 | field attribute, options do |opts|
97 | opts.merge!(update_elements: opts[:update_elements],
98 | min_length: 0, value: object.send(attribute))
99 | autocomplete_field(attribute, url, opts)
100 | end
101 | end
102 |
103 | def submit(value = nil, options = {})
104 | options[:class] ||= FoundationRailsHelper.configuration.button_class
105 | super(value, options)
106 | end
107 |
108 | def error_for(attribute, options = {})
109 | return unless has_error?(attribute)
110 |
111 | class_name = 'form-error is-visible'
112 | class_name += " #{options[:class]}" if options[:class]
113 |
114 | error_messages = object.errors[attribute].join(', ')
115 | error_messages = error_messages.html_safe if options[:html_safe_errors]
116 | content_tag(:small, error_messages, class: class_name.sub('is-invalid-input', ''))
117 | end
118 |
119 | private
120 |
121 | def has_error?(attribute)
122 | object.respond_to?(:errors) && !object.errors[attribute].blank?
123 | end
124 |
125 | def custom_label(attribute, text, options)
126 | return block_given? ? yield.html_safe : ''.html_safe if text == false
127 | if text.nil? || text == true
128 | text =
129 | if object.class.respond_to?(:human_attribute_name)
130 | object.class.human_attribute_name(attribute)
131 | else
132 | attribute.to_s.humanize
133 | end
134 | end
135 | text = yield.html_safe + " #{text}" if block_given?
136 | options ||= {}
137 | label(attribute, text, options)
138 | end
139 |
140 | def column_classes(options)
141 | classes = SizeClassCalcluator.new(options).classes
142 | classes + ' columns'
143 | end
144 |
145 | class SizeClassCalcluator
146 | def initialize(size_options)
147 | @small = size_options[:small]
148 | @medium = size_options[:medium]
149 | @large = size_options[:large]
150 | end
151 |
152 | def classes
153 | [small_class, medium_class, large_class].compact.join(' ')
154 | end
155 |
156 | private
157 |
158 | def small_class
159 | "small-#{@small}" if valid_size(@small)
160 | end
161 |
162 | def medium_class
163 | "medium-#{@medium}" if valid_size(@medium)
164 | end
165 |
166 | def large_class
167 | "large-#{@large}" if valid_size(@large)
168 | end
169 |
170 | def valid_size(value)
171 | value.present? && value.to_i < 12
172 | end
173 | end
174 |
175 | def tag_from_options(name, options)
176 | return ''.html_safe unless options && options[:value].present?
177 |
178 | content_tag(:div,
179 | content_tag(:span, options[:value], class: name),
180 | class: column_classes(options).to_s)
181 | end
182 |
183 | def decrement_input_size(input, column, options)
184 | return unless options.key?(column)
185 |
186 | input.send("#{column}=",
187 | (input.send(column) - options.fetch(column).to_i))
188 | input.send('changed?=', true)
189 | end
190 |
191 | def calculate_input_size(prefix_options, postfix_options)
192 | input_size =
193 | OpenStruct.new(changed?: false, small: 12, medium: 12, large: 12)
194 | if prefix_options.present?
195 | %w(small medium large).each do |size|
196 | decrement_input_size(input_size, size.to_sym, prefix_options)
197 | end
198 | end
199 | if postfix_options.present?
200 | %w(small medium large).each do |size|
201 | decrement_input_size(input_size, size.to_sym, postfix_options)
202 | end
203 | end
204 |
205 | input_size
206 | end
207 |
208 | def wrap_prefix_and_postfix(block, prefix_options, postfix_options)
209 | prefix = tag_from_options('prefix', prefix_options)
210 | postfix = tag_from_options('postfix', postfix_options)
211 |
212 | input_size = calculate_input_size(prefix_options, postfix_options)
213 | klass = column_classes(input_size.marshal_dump).to_s
214 | input = content_tag(:div, block, class: klass)
215 |
216 | html =
217 | if input_size.changed?
218 | content_tag(:div, prefix + input + postfix, class: 'row collapse')
219 | else
220 | block
221 | end
222 |
223 | html.html_safe
224 | end
225 |
226 | def error_and_help_text(attribute, options = {})
227 | html = ''
228 | if options[:help_text]
229 | html += content_tag(:p, options[:help_text], class: 'help-text')
230 | end
231 | html += error_for(attribute, options) || ''
232 | html.html_safe
233 | end
234 |
235 | def field(attribute, options, html_options = nil)
236 | auto_labels = true unless @options[:auto_labels] == false
237 | html = if auto_labels || options[:label]
238 | custom_label(attribute, options[:label], options[:label_options])
239 | else
240 | ''.html_safe
241 | end
242 | class_options = html_options || options
243 |
244 | if has_error?(attribute)
245 | class_options[:class] = class_options[:class].to_s
246 | class_options[:class] += ' is-invalid-input'
247 | end
248 |
249 | options.delete(:label)
250 | options.delete(:label_options)
251 | help_text = options.delete(:help_text)
252 | prefix = options.delete(:prefix)
253 | postfix = options.delete(:postfix)
254 |
255 | html += wrap_prefix_and_postfix(yield(class_options), prefix, postfix)
256 | html + error_and_help_text(attribute, options.merge(help_text: help_text))
257 | end
258 | end
259 | end
260 |
--------------------------------------------------------------------------------
/spec/foundation_rails_helper/form_builder_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe "FoundationRailsHelper::FormHelper" do
4 | include FoundationRailsSpecHelper
5 |
6 | before do
7 | mock_everything
8 | end
9 |
10 | it "should have FoundationRailsHelper::FormHelper as default buidler" do
11 | form_for(@author) do |builder|
12 | expect(builder.class).to eq FoundationRailsHelper::FormBuilder
13 | end
14 | end
15 |
16 | it "should display labels by default" do
17 | form_for(@author) do |builder|
18 | node = Capybara.string builder.text_field(:login)
19 | expect(node).to have_css('label[for="author_login"]', text: "Login")
20 | end
21 | end
22 |
23 | it "should display labels if auto_labels: true is set" do
24 | form_for(@author, auto_labels: true) do |builder|
25 | node = Capybara.string builder.text_field(:login)
26 | expect(node).to have_css('label[for="author_login"]', text: "Login")
27 | end
28 | end
29 |
30 | it "should not display labels by if there are options without auto_labels: false" do
31 | form_for(@author, html: { class: "myclass" }) do |builder|
32 | node = Capybara.string builder.text_field(:login)
33 | expect(node).to have_css('label[for="author_login"]', text: "Login")
34 | end
35 | end
36 |
37 | it "should not display labels if there are options with auto_labels: false" do
38 | form_for(@author, html: { class: "myclass" }, auto_labels: false) do |builder|
39 | node = Capybara.string builder.text_field(:login)
40 | expect(node).to_not have_css('label[for="author_login"]', text: "Login")
41 | end
42 | end
43 |
44 | it "should display labels if :auto_labels is set to nil" do
45 | form_for(@author, auto_labels: nil) do |builder|
46 | node = Capybara.string builder.text_field(:login)
47 | expect(node).to have_css('label[for="author_login"]', text: "Login")
48 | end
49 | end
50 |
51 | it "should display labels if :auto_labels is set to a string" do
52 | form_for(@author, auto_labels: "false") do |builder|
53 | node = Capybara.string builder.text_field(:login)
54 | expect(node).to have_css('label[for="author_login"]', text: "Login")
55 | end
56 | end
57 |
58 | describe "label" do
59 | context "when there aren't any errors and no class option is passed" do
60 | it "should not have a class attribute" do
61 | form_for(@author) do |builder|
62 | node = Capybara.string builder.text_field(:login)
63 | expect(node).to have_css('label:not([class=""])')
64 | end
65 | end
66 | end
67 |
68 | it "should not have error class multiple times" do
69 | form_for(@author) do |builder|
70 | allow(@author).to receive(:errors).and_return(login: ["required"])
71 | node = Capybara.string builder.text_field(:login)
72 | error_class = node.find("label")["class"].split(/\s+/).keep_if { |v| v == "is-invalid-label" }
73 | expect(error_class.size).to eq 1
74 | end
75 | end
76 | end
77 |
78 | describe "prefix" do
79 | context "when input field has a prefix" do
80 | before do
81 | form_for(@author) do |builder|
82 | @node = Capybara.string builder.text_field(:login, prefix: { small: 2, medium: 4, large: 6, value: "Prefix" })
83 | end
84 | end
85 |
86 | it "wraps input in the div with class 'row collapse'" do
87 | expect(@node.find(".row.collapse")).to_not be nil
88 | end
89 |
90 | it "wraps prefix in the div with the right column size" do
91 | expect(@node.find(".row.collapse")).to have_css("div.small-2.medium-4.large-6.columns")
92 | end
93 |
94 | it "creates prefix span with right value" do
95 | expect(@node.find(".row.collapse").find("div.small-2.medium-4.large-6.columns").find("span").text).to eq "Prefix"
96 | end
97 |
98 | it "creates prefix span with right class" do
99 | expect(@node.find(".row.collapse")).to have_css("span.prefix")
100 | end
101 |
102 | it "wraps input in the div with the right column size" do
103 | expect(@node.find(".row.collapse")).to have_css("div.small-10.medium-8.large-6.columns")
104 | end
105 |
106 | it "has right value for the input" do
107 | expect(@node.find(".row.collapse").find("div.small-10.medium-8.large-6.columns")).to have_css('input[type="text"][name="author[login]"]')
108 | end
109 | end
110 |
111 | context "without prefix" do
112 | it "will not wrap input into a div" do
113 | form_for(@author) do |builder|
114 | node = Capybara.string builder.text_field(:login)
115 | expect(node).to_not have_css("div.row.collapse")
116 | end
117 | end
118 | end
119 | end
120 |
121 | describe "postfix" do
122 | context "when input field has a postfix" do
123 | before do
124 | form_for(@author) do |builder|
125 | @node = Capybara.string builder.text_field(:login, postfix: { small: 2, medium: 4, large: 6, value: "Postfix" })
126 | end
127 | end
128 |
129 | it "wraps input in the div with class 'row collapse'" do
130 | expect(@node.find(".row.collapse")).to_not be nil
131 | end
132 |
133 | it "wraps postfix in the div with the right column size" do
134 | expect(@node.find(".row.collapse")).to have_css("div.small-2.medium-4.large-6.columns")
135 | end
136 |
137 | it "creates postfix span with right value" do
138 | expect(@node.find(".row.collapse").find("div.small-2.medium-4.large-6.columns").find("span").text).to eq "Postfix"
139 | end
140 |
141 | it "creates postfix span with right class" do
142 | expect(@node.find(".row.collapse")).to have_css("span.postfix")
143 | end
144 |
145 | it "wraps input in the div with the right column size" do
146 | expect(@node.find(".row.collapse")).to have_css("div.small-10.medium-8.large-6.columns")
147 | end
148 |
149 | it "has right value for the input" do
150 | expect(@node.find(".row.collapse").find("div.small-10.medium-8.large-6.columns")).to have_css('input[type="text"][name="author[login]"]')
151 | end
152 | end
153 |
154 | context "with only one column size" do
155 | before do
156 | form_for(@author) do |builder|
157 | @small_node = Capybara.string builder.text_field(:login, postfix: { small: 2, value: "Postfix" })
158 | @medium_node = Capybara.string builder.text_field(:login, postfix: { medium: 2, value: "Postfix" })
159 | @large_node = Capybara.string builder.text_field(:login, postfix: { large: 2, value: "Postfix" })
160 | end
161 | end
162 |
163 | it "wraps postfix in the div with the right column size" do
164 | expect(@small_node.find(".row.collapse")).to have_css("div.small-2.columns")
165 | expect(@medium_node.find(".row.collapse")).to have_css("div.medium-2.columns")
166 | expect(@large_node.find(".row.collapse")).to have_css("div.large-2.columns")
167 | end
168 |
169 | it "wraps input in the div with the right column size" do
170 | expect(@small_node.find(".row.collapse")).to have_css("div.small-10.columns")
171 | expect(@medium_node.find(".row.collapse")).to have_css("div.medium-10.columns")
172 | expect(@large_node.find(".row.collapse")).to have_css("div.large-10.columns")
173 | end
174 |
175 | it "excludes other classes from the prefix" do
176 | expect(@small_node.find(".row.collapse")).to_not have_css("div.medium-2.columns")
177 | expect(@small_node.find(".row.collapse")).to_not have_css("div.large-2.columns")
178 | end
179 |
180 | it "excludes other classes from the input" do
181 | expect(@small_node.find(".row.collapse")).to have_css("div.small-10.columns")
182 | expect(@small_node.find(".row.collapse")).to_not have_css("div.medium-12.columns")
183 | expect(@small_node.find(".row.collapse")).to_not have_css("div.large-12.columns")
184 | end
185 | end
186 | end
187 |
188 | describe "with both prefix and postfix" do
189 | context "when input field has a prefix" do
190 | before do
191 | form_for(@author) do |builder|
192 | @node = Capybara.string builder.text_field(:login,
193 | prefix: { small: 2, medium: 3, large: 4, value: "Prefix" },
194 | postfix: { small: 2, medium: 3, large: 4, value: "Postfix" })
195 | end
196 | end
197 |
198 | it "wraps input in the div with the right column size" do
199 | expect(@node.find(".row.collapse")).to have_css("div.small-8.medium-6.large-4.columns")
200 | end
201 | end
202 | end
203 |
204 | describe "input generators" do
205 | it "should generate text_field input" do
206 | form_for(@author) do |builder|
207 | node = Capybara.string builder.text_field(:login)
208 | expect(node).to have_css('label[for="author_login"]', text: "Login")
209 | expect(node).to have_css('input[type="text"][name="author[login]"]')
210 | expect(node.find_field("author_login").value).to eq @author.login
211 | end
212 | end
213 |
214 | it "should generate text_field input without label" do
215 | form_for(@author) do |builder|
216 | node = Capybara.string builder.text_field(:login, label: false)
217 | expect(node).to_not have_css('label[for="author_login"]', text: "Login")
218 | expect(node).to have_css('input[type="text"][name="author[login]"]')
219 | expect(node.find_field("author_login").value).to eq @author.login
220 | end
221 | end
222 |
223 | it "should generate text_field with class from options" do
224 | form_for(@author) do |builder|
225 | node = Capybara.string builder.text_field(:login, class: "righteous")
226 | expect(node).to have_css('input.righteous[type="text"][name="author[login]"]')
227 | end
228 | end
229 |
230 | it "should generate password_field input" do
231 | form_for(@author) do |builder|
232 | node = Capybara.string builder.password_field(:password)
233 | expect(node).to have_css('label[for="author_password"]', text: "Password")
234 | expect(node).to have_css('input[type="password"][name="author[password]"]')
235 | expect(node.find_field("author_password").value).to be_nil
236 | end
237 | end
238 |
239 | it "should generate email_field input" do
240 | form_for(@author) do |builder|
241 | node = Capybara.string builder.email_field(:email)
242 | expect(node).to have_css('label[for="author_email"]', text: "Email")
243 | expect(node).to have_css('input[type="email"][name="author[email]"]')
244 | expect(node.find_field("author_email").value).to eq @author.email
245 | end
246 | end
247 |
248 | it "should generate url_field input" do
249 | form_for(@author) do |builder|
250 | node = Capybara.string builder.url_field(:url)
251 | expect(node).to have_css('label[for="author_url"]', text: "Url")
252 | expect(node).to have_css('input[type="url"][name="author[url]"]')
253 | expect(node.find_field("author_url").value).to eq @author.url
254 | end
255 | end
256 |
257 | it "should generate phone_field input" do
258 | form_for(@author) do |builder|
259 | node = Capybara.string builder.phone_field(:phone)
260 | expect(node).to have_css('label[for="author_phone"]', text: "Phone")
261 | expect(node).to have_css('input[type="tel"][name="author[phone]"]')
262 | expect(node.find_field("author_phone").value).to eq @author.phone
263 | end
264 | end
265 |
266 | it "should generate number_field input" do
267 | form_for(@author) do |builder|
268 | node = Capybara.string builder.number_field(:some_number)
269 | expect(node).to have_css('label[for="author_some_number"]', text: "Some number")
270 | expect(node).to have_css('input[type="number"][name="author[some_number]"]')
271 | expect(node.find_field("author_some_number").value).to eq @author.some_number
272 | end
273 | end
274 |
275 | it "should generate text_area input" do
276 | form_for(@author) do |builder|
277 | node = Capybara.string builder.text_area(:description)
278 | expect(node).to have_css('label[for="author_description"]', text: "Description")
279 | expect(node).to have_css('textarea[name="author[description]"]')
280 | expect(node.find_field("author_description").value.strip).to eq @author.description
281 | end
282 | end
283 |
284 | it "should generate file_field input" do
285 | form_for(@author) do |builder|
286 | node = Capybara.string builder.file_field(:avatar)
287 | expect(node).to have_css('label[for="author_avatar"]', text: "Avatar")
288 | expect(node).to have_css('input[type="file"][name="author[avatar]"]')
289 | expect(node.find_field("author_avatar").value).to be_nil
290 | end
291 | end
292 |
293 | it "should generate select input" do
294 | form_for(@author) do |builder|
295 | node = Capybara.string builder.select(:description, [["Choice #1", :a], ["Choice #2", :b]])
296 | expect(node).to have_css('label[for="author_description"]', text: "Description")
297 | expect(node).to have_css('select[name="author[description]"]')
298 | expect(node).to have_css('select[name="author[description]"] option[value="a"]', text: "Choice #1")
299 | expect(node).to have_css('select[name="author[description]"] option[value="b"]', text: "Choice #2")
300 | end
301 | end
302 |
303 | it "should generate check_box input" do
304 | form_for(@author) do |builder|
305 | node = Capybara.string builder.check_box(:active)
306 | expect(node).to have_css('label[for="author_active"] input[type="hidden"][name="author[active]"][value="0"]', visible: false)
307 | expect(node).to have_css('label[for="author_active"] input[type="checkbox"][name="author[active]"]')
308 | expect(node).to have_css('label[for="author_active"]', text: "Active")
309 | end
310 | end
311 | it "should generate check_box input without a label" do
312 | form_for(@author) do |builder|
313 | node = Capybara.string builder.check_box(:active, label: false)
314 | expect(node).to have_css('input[type="hidden"][name="author[active]"][value="0"]', visible: false)
315 | expect(node).to have_css('input[type="checkbox"][name="author[active]"]')
316 | expect(node).to_not have_css('label[for="author_active"]')
317 | end
318 | end
319 |
320 | it "should generate radio_button input" do
321 | form_for(@author) do |builder|
322 | node = Capybara.string builder.radio_button(:active, "ok")
323 | expect(node).to have_css('label[for="author_active_ok"]')
324 | expect(node).to have_css('input[type="radio"][name="author[active]"]')
325 | end
326 | end
327 | it "should generate radio_button input with a label" do
328 | form_for(@author) do |builder|
329 | node = Capybara.string builder.radio_button(:active, true, label: "Functioning")
330 | expect(node).to have_css('label[for="author_active_true"]', text: "Functioning")
331 | expect(node).to have_css('input[type="radio"][name="author[active]"]')
332 | end
333 | end
334 | it "should generate radio_button without a label" do
335 | form_for(@author) do |builder|
336 | node = Capybara.string builder.radio_button(:active, "ok", label: false)
337 | expect(node).to_not have_css('label[for="author_active_ok"]')
338 | expect(node).to_not have_css('input[label="false"]')
339 | expect(node).to have_css('input[type="radio"][name="author[active]"]')
340 | end
341 | end
342 | it "should generate radio_button with label options" do
343 | form_for(@author) do |builder|
344 | node = Capybara.string builder.radio_button(:active, "ok", class: "very", label_options: { class: "special" })
345 | expect(node).to have_css('label.special[for="author_active_ok"]')
346 | expect(node).to have_css('input.very[type="radio"][name="author[active]"]')
347 | end
348 | end
349 |
350 | it "should generate date_select input" do
351 | form_for(@author) do |builder|
352 | node = Capybara.string builder.label(:birthdate) + builder.date_select(:birthdate)
353 | expect(node).to have_css('label[for="author_birthdate"]', text: "Birthdate")
354 | %w(1 2 3).each { |i| expect(node).to have_css("select[name='author[birthdate(#{i}i)]']") }
355 | expect(node).to have_css('select#author_birthdate_1i option[selected="selected"][value="1969"]')
356 | expect(node).to have_css('select#author_birthdate_2i option[selected="selected"][value="6"]')
357 | expect(node).to have_css('select#author_birthdate_3i option[selected="selected"][value="18"]')
358 | %w(4 5).each { |i| expect(node).to_not have_css("select[name='author[birthdate(#{i}i)]']") }
359 | end
360 | end
361 |
362 | it "should generate date_select input with :discard_year => true" do
363 | form_for(@author) do |builder|
364 | node = Capybara.string builder.label(:birthdate) + builder.date_select(:birthdate, discard_year: true)
365 | expect(node).to have_css('label[for="author_birthdate"]', text: "Birthdate")
366 | %w(2 3).each { |i| expect(node).to have_css("select[name='author[birthdate(#{i}i)]']") }
367 | expect(node).to_not have_css('select#author_birthdate_1i option[selected="selected"][value="1969"]')
368 | expect(node).to have_css('select#author_birthdate_2i option[selected="selected"][value="6"]')
369 | expect(node).to have_css('select#author_birthdate_3i option[selected="selected"][value="18"]')
370 | %w(1 4 5).each { |i| expect(node).to_not have_css("select[name='author[birthdate(#{i}i)]']") }
371 | end
372 | end
373 |
374 | it "should generate datetime_select input" do
375 | form_for(@author) do |builder|
376 | node = Capybara.string builder.label(:birthdate) + builder.datetime_select(:birthdate)
377 | expect(node).to have_css('label[for="author_birthdate"]', text: "Birthdate")
378 | %w(1 2 3 4 5).each { |i| expect(node).to have_css("select[name='author[birthdate(#{i}i)]']") }
379 | expect(node).to have_css('select#author_birthdate_1i option[selected="selected"][value="1969"]')
380 | expect(node).to have_css('select#author_birthdate_2i option[selected="selected"][value="6"]')
381 | expect(node).to have_css('select#author_birthdate_3i option[selected="selected"][value="18"]')
382 | expect(node).to have_css('select#author_birthdate_4i option[selected="selected"][value="20"]')
383 | expect(node).to have_css('select#author_birthdate_5i option[selected="selected"][value="30"]')
384 | end
385 | end
386 |
387 | it "should generate datetime_select input with :discard_year => true" do
388 | form_for(@author) do |builder|
389 | node = Capybara.string builder.label(:birthdate) + builder.datetime_select(:birthdate, discard_year: true)
390 | expect(node).to have_css('label[for="author_birthdate"]', text: "Birthdate")
391 | %w(2 3 4 5).each { |i| expect(node).to have_css("select[name='author[birthdate(#{i}i)]']") }
392 | expect(node).to_not have_css('select#author_birthdate_1i option[selected="selected"][value="1969"]')
393 | expect(node).to have_css('select#author_birthdate_2i option[selected="selected"][value="6"]')
394 | expect(node).to have_css('select#author_birthdate_3i option[selected="selected"][value="18"]')
395 | expect(node).to have_css('select#author_birthdate_4i option[selected="selected"][value="20"]')
396 | expect(node).to have_css('select#author_birthdate_5i option[selected="selected"][value="30"]')
397 | %w(1).each { |i| expect(node).to_not have_css("select[name='author[birthdate(#{i}i)]']") }
398 | end
399 | end
400 |
401 | it "should generate time_zone_select input" do
402 | form_for(@author) do |builder|
403 | node = Capybara.string builder.label(:time_zone) + builder.time_zone_select(:time_zone)
404 | expect(node).to have_css('label[for="author_time_zone"]', text: "Time zone")
405 | expect(node).to have_css('select[name="author[time_zone]"]')
406 | expect(node).to have_css('select[name="author[time_zone]"] option[value="Perth"]', text: "(GMT+08:00) Perth")
407 | end
408 | end
409 |
410 | it "should generate date_field input" do
411 | form_for(@author) do |builder|
412 | node = Capybara.string builder.date_field(:publish_date)
413 | expect(node).to have_css('label[for="author_publish_date"]', text: "date")
414 | expect(node).to have_css('input[type="date"][name="author[publish_date]"]')
415 | expect(node.find_field("author_publish_date").value).to eq @author.publish_date.to_s
416 | end
417 | end
418 |
419 | it "should generate datetime_field input" do
420 | form_for(@author) do |builder|
421 | node = Capybara.string builder.datetime_field(:forty_two)
422 | expect(node).to have_css('label[for="author_forty_two"]', text: "Forty two")
423 | expect(node).to have_css('input[type^="datetime"][name="author[forty_two]"]')
424 | value = DateTime.parse(node.find_field("author_forty_two").value)
425 | expect(value).to eq @author.forty_two.to_s
426 | end
427 | end
428 |
429 | it "should generate datetime_local_field" do
430 | form_for(@author) do |builder|
431 | node = Capybara.string builder.datetime_local_field(:forty_two)
432 | expect(node).to have_css('label[for="author_forty_two"]', text: "Forty two")
433 | expect(node).to have_css('input[type="datetime-local"][name="author[forty_two]"]')
434 | expect(node.find_field("author_forty_two").value).to eq @author.forty_two.strftime("%Y-%m-%dT%H:%M:%S")
435 | end
436 | end
437 |
438 | it "should generate month_field input" do
439 | form_for(@author) do |builder|
440 | node = Capybara.string builder.month_field(:forty_two)
441 | expect(node).to have_css('label[for="author_forty_two"]', text: "Forty two")
442 | expect(node).to have_css('input[type="month"][name="author[forty_two]"]')
443 | expect(node.find_field("author_forty_two").value).to eq @author.forty_two.strftime("%Y-%m")
444 | end
445 | end
446 |
447 | it "should generate week_field" do
448 | form_for(@author) do |builder|
449 | node = Capybara.string builder.week_field(:forty_two)
450 | expect(node).to have_css('label[for="author_forty_two"]', text: "Forty two")
451 | expect(node).to have_css('input[type="week"][name="author[forty_two]"]')
452 | expect(node.find_field("author_forty_two").value).to eq @author.forty_two.strftime("%Y-W%V")
453 | end
454 | end
455 |
456 | it "should generate time_field" do
457 | form_for(@author) do |builder|
458 | node = Capybara.string builder.time_field(:forty_two)
459 | expect(node).to have_css('label[for="author_forty_two"]', text: "Forty two")
460 | expect(node).to have_css('input[type="time"][name="author[forty_two]"]')
461 | expect(node.find_field("author_forty_two").value).to eq @author.forty_two.strftime("%H:%M:%S.%L")
462 | end
463 | end
464 |
465 | it "should generate range_field" do
466 | form_for(@author) do |builder|
467 | node = Capybara.string builder.range_field(:some_number)
468 | expect(node).to have_css('label[for="author_some_number"]', text: "Some number")
469 | expect(node).to have_css('input[type="range"][name="author[some_number]"]')
470 | expect(node.find_field("author_some_number").value).to eq @author.some_number
471 | end
472 | end
473 |
474 | it "should generate search_field" do
475 | form_for(@author) do |builder|
476 | node = Capybara.string builder.search_field(:description)
477 | expect(node).to have_css('label[for="author_description"]', text: "Description")
478 | expect(node).to have_css('input[type="search"][name="author[description]"]')
479 | expect(node.find_field("author_description").value).to eq @author.description
480 | end
481 | end
482 |
483 | it "should generate color_field" do
484 | form_for(@author) do |builder|
485 | node = Capybara.string builder.color_field(:favorite_color)
486 | expect(node).to have_css('label[for="author_favorite_color"]', text: "Favorite color")
487 | expect(node).to have_css('input[type="color"][name="author[favorite_color]"]')
488 | expect(node.find_field("author_favorite_color").value).to eq @author.favorite_color
489 | end
490 | end
491 |
492 | it "should generate collection_select input" do
493 | form_for(@author) do |builder|
494 | node = Capybara.string builder.collection_select(:favorite_book, Book.all, :id, :title)
495 | expect(node).to have_css('label[for="author_favorite_book"]', text: "Favorite book")
496 | expect(node).to have_css('select[name="author[favorite_book]"]')
497 | expect(node).to have_css('select[name="author[favorite_book]"] option[value="78"]', text: "Gulliver's Travels")
498 | expect(node).to have_css('select[name="author[favorite_book]"] option[value="133"]', text: "Treasure Island")
499 | end
500 | end
501 |
502 | it "should generate grouped_collection_select input" do
503 | form_for(@author) do |builder|
504 | node = Capybara.string builder.grouped_collection_select(:favorite_book, Genre.all, :books, :name, :id, :title)
505 | expect(node).to have_css('label[for="author_favorite_book"]', text: "Favorite book")
506 | expect(node).to have_css('select[name="author[favorite_book]"]')
507 | expect(node).to have_css('select[name="author[favorite_book]"] optgroup[label="Exploration"] option[value="78"]', text: "Gulliver's Travels")
508 | expect(node).to have_css('select[name="author[favorite_book]"] optgroup[label="Pirate Exploits"] option[value="133"]', text: "Treasure Island")
509 | end
510 | end
511 |
512 | describe "help_text" do
513 | it "should add a p element" do
514 | form_for(@author) do |builder|
515 | help_text = "Enter login"
516 | node = Capybara.string builder.text_field(:login, help_text: help_text)
517 | expect(node.find("p").text).to eq help_text
518 | end
519 | end
520 |
521 | it "should not add help_text attribute" do
522 | form_for(@author) do |builder|
523 | node = Capybara.string builder.text_field(:login, help_text: "Enter login")
524 | expect(node.find_field("author_login")["help_text"]).to be_nil
525 | end
526 | end
527 | end
528 |
529 | context "when there aren't any errors and no class option is passed" do
530 | it "should not have a class attribute" do
531 | form_for(@author) do |builder|
532 | node = Capybara.string builder.text_field(:login)
533 | expect(node).to have_css('input:not([class=""])')
534 | end
535 | end
536 | end
537 | end
538 |
539 | describe "errors generator" do
540 | it "should not display errors" do
541 | form_for(@author) do |builder|
542 | node = Capybara.string builder.text_field(:login)
543 | expect(node).to_not have_css("small.form-error")
544 | end
545 | end
546 | it "should display errors" do
547 | form_for(@author) do |builder|
548 | allow(@author).to receive(:errors).and_return(login: ["required"])
549 | node = Capybara.string builder.text_field(:login)
550 | expect(node).to have_css("small.form-error", text: "required")
551 | end
552 | end
553 | %w(file_field email_field text_field telephone_field phone_field
554 | url_field number_field date_field datetime_field datetime_local_field
555 | month_field week_field time_field range_field search_field color_field
556 |
557 | password_field).each do |field|
558 | it "should display errors on #{field} inputs" do
559 | form_for(@author) do |builder|
560 | allow(@author).to receive(:errors).and_return(description: ["required"])
561 | node = Capybara.string builder.public_send(field, :description)
562 | expect(node).to have_css('label.is-invalid-label[for="author_description"]')
563 | expect(node).to have_css('input.is-invalid-input[name="author[description]"]')
564 | end
565 | end
566 | end
567 | it "should display errors on text_area inputs" do
568 | form_for(@author) do |builder|
569 | allow(@author).to receive(:errors).and_return(description: ["required"])
570 | node = Capybara.string builder.text_area(:description)
571 | expect(node).to have_css('label.is-invalid-label[for="author_description"]')
572 | expect(node).to have_css('textarea.is-invalid-input[name="author[description]"]')
573 | end
574 | end
575 | it "should display errors on select inputs" do
576 | form_for(@author) do |builder|
577 | allow(@author).to receive(:errors).and_return(favorite_book: ["required"])
578 | node = Capybara.string builder.select(:favorite_book, [["Choice #1", :a], ["Choice #2", :b]])
579 | expect(node).to have_css('label.is-invalid-label[for="author_favorite_book"]')
580 | expect(node).to have_css('select.is-invalid-input[name="author[favorite_book]"]')
581 | end
582 | end
583 | it "should display errors on date_select inputs" do
584 | form_for(@author) do |builder|
585 | allow(@author).to receive(:errors).and_return(birthdate: ["required"])
586 | node = Capybara.string builder.date_select(:birthdate)
587 | expect(node).to have_css('label.is-invalid-label[for="author_birthdate"]')
588 | %w(1 2 3).each { |i| expect(node).to have_css("select.is-invalid-input[name='author[birthdate(#{i}i)]']") }
589 | end
590 | end
591 | it "should display errors on datetime_select inputs" do
592 | form_for(@author) do |builder|
593 | allow(@author).to receive(:errors).and_return(birthdate: ["required"])
594 | node = Capybara.string builder.datetime_select(:birthdate)
595 | expect(node).to have_css('label.is-invalid-label[for="author_birthdate"]')
596 | %w(1 2 3 4 5).each { |i| expect(node).to have_css("select.is-invalid-input[name='author[birthdate(#{i}i)]']") }
597 | end
598 | end
599 | it "should display errors on time_zone_select inputs" do
600 | form_for(@author) do |builder|
601 | allow(@author).to receive(:errors).and_return(time_zone: ["required"])
602 | node = Capybara.string builder.time_zone_select(:time_zone)
603 | expect(node).to have_css('label.is-invalid-label[for="author_time_zone"]')
604 | expect(node).to have_css('select.is-invalid-input[name="author[time_zone]"]')
605 | end
606 | end
607 |
608 | it "should display errors on collection_select inputs" do
609 | form_for(@author) do |builder|
610 | allow(@author).to receive(:errors).and_return(favorite_book: ["required"])
611 | node = Capybara.string builder.collection_select(:favorite_book, Book.all, :id, :title)
612 | expect(node).to have_css('label.is-invalid-label[for="author_favorite_book"]')
613 | expect(node).to have_css('select.is-invalid-input[name="author[favorite_book]"]')
614 | end
615 | end
616 |
617 | it "should display errors on grouped_collection_select inputs" do
618 | form_for(@author) do |builder|
619 | allow(@author).to receive(:errors).and_return(favorite_book: ["required"])
620 | node = Capybara.string builder.grouped_collection_select(:favorite_book, Genre.all, :books, :name, :id, :title)
621 | expect(node).to have_css('label.is-invalid-label[for="author_favorite_book"]')
622 | expect(node).to have_css('select.is-invalid-input[name="author[favorite_book]"]')
623 | end
624 | end
625 |
626 | # N.B. check_box and radio_button inputs don't have the is-invalid-input class applied
627 |
628 | it "should display HTML errors when the option is specified" do
629 | form_for(@author) do |builder|
630 | allow(@author).to receive(:errors).and_return(login: ['required link'])
631 | node = Capybara.string builder.text_field(:login, html_safe_errors: true)
632 | expect(node).to have_link("link", href: "link_target")
633 | end
634 | end
635 | it "should not display HTML errors when the option is not specified" do
636 | form_for(@author) do |builder|
637 | allow(@author).to receive(:errors).and_return(login: ['required link'])
638 | node = Capybara.string builder.text_field(:login)
639 | expect(node).to_not have_link("link", href: "link")
640 | end
641 | end
642 |
643 | it "should not display labels unless specified in the builder method" do
644 | form_for(@author, auto_labels: false) do |builder|
645 | node = Capybara.string builder.text_field(:login) +
646 | builder.check_box(:active, label: true) +
647 | builder.text_field(:description, label: "Tell me about you")
648 |
649 | expect(node).to_not have_css('label[for="author_login"]')
650 | expect(node).to have_css('label[for="author_active"]', text: "Active")
651 | expect(node).to have_css('label[for="author_description"]', text: "Tell me about you")
652 | end
653 | end
654 |
655 | context "when class option given" do
656 | it "should add it to the error class" do
657 | form_for(@author) do |builder|
658 | allow(@author).to receive(:errors).and_return(email: ["required"])
659 | node = Capybara.string builder.text_field(:email, class: "righteous")
660 | expect(node).to have_css('input.righteous.is-invalid-input[name="author[email]"]')
661 | end
662 | end
663 | end
664 |
665 | context "when invalid class option given" do
666 | it "should add it to the error class" do
667 | form_for(@author) do |builder|
668 | allow(@author).to receive(:errors).and_return(email: ["required"])
669 | node = Capybara.string builder.text_field(:email, class: :illgotten)
670 | expect(node).to have_css('input.illgotten.is-invalid-input[name="author[email]"]')
671 | end
672 | end
673 | end
674 | end
675 |
676 | describe "submit button generator" do
677 | after :each do
678 | FoundationRailsHelper.reset
679 | end
680 |
681 | context "when button_class config is not set" do
682 | it "should display form button with default class" do
683 | form_for(@author) do |builder|
684 | node = Capybara.string builder.submit("Save")
685 | expect(node).to have_css('input[type="submit"][class="success button"]')
686 | end
687 | end
688 | end
689 |
690 | context 'when button_class config is "superduper"' do
691 | before do
692 | FoundationRailsHelper.configure do |config|
693 | config.button_class = "superduper"
694 | end
695 | end
696 |
697 | it "should display form button with 'superduper' class" do
698 | form_for(@author) do |builder|
699 | node = Capybara.string builder.submit("Save")
700 | expect(node).to have_css('input[type="submit"][class="superduper"]')
701 | end
702 | end
703 | end
704 |
705 | context 'when option value is "superduper"' do
706 | it "should display form button with 'superduper' class" do
707 | form_for(@author) do |builder|
708 | node = Capybara.string builder.submit("Save", class: "superduper")
709 | expect(node).to have_css('input[type="submit"][class="superduper"]')
710 | end
711 | end
712 | end
713 | end
714 | end
715 |
--------------------------------------------------------------------------------