├── .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 [![Build Status](https://secure.travis-ci.org/sgruhier/foundation_rails_helper.png)](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 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 52 | 53 | 54 |
FormForm with errors
47 | 48 | 50 | 51 |
55 | 56 | ### Flash messages 57 | 58 | ![Flash-message](https://cloud.githubusercontent.com/assets/1400414/18522256/3d13c97e-7a64-11e6-9ee2-33adc93cd573.png "Flash-message") 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 |
95 | ... 96 |
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 | --------------------------------------------------------------------------------