Merge branch 'master' into kb-ek-change-wraper-collection

Conflicts:
	CHANGELOG.md
	lib/simple_form/tags.rb
This commit is contained in:
Erich Kist 2013-12-09 11:11:42 -02:00
commit e8e1973d17
24 changed files with 305 additions and 96 deletions

View File

@ -2,6 +2,12 @@ language: ruby
rvm:
- 1.9.3
- 2.0.0
gemfile:
- gemfiles/Gemfile.rails-head
- Gemfile
matrix:
allow_failures:
- gemfile: gemfiles/Gemfile.rails-head
notifications:
email: false
campfire:

View File

@ -2,12 +2,21 @@
### enhancements
* For radio or checkbox collection always use `:item_wrapper_tag` to wrap the content and add `label` when using `boolean_style` with `:nested` [@kassio](https://github.com/kassio) and [@bernardoamc](https://github.com/bernardoamc)
* `input_field` uses the same wrapper as input but only with attribute components. [@nashby](https://github.com/nashby)
* Add wrapper mapping per form basis [@rcillo](https://github.com/rcillo) and [@bernardoamc](https://github.com/bernardoamc)
* Add `for` attribute to `label` when collections are rendered as radio or checkbox [@erichkist](https://github.com/erichkist), [@ulissesalmeida](https://github.com/ulissesalmeida) and [@fabioyamate](https://github.com/fabioyamate)
* Add `include_default_input_wrapper_class` config [@luizcosta](https://github.com/luizcosta)
* Map `datetime`, `date` and `time` input types to their respective HTML5 input tags
when the `:html5` is set to `true` [@volmer](https://github.com/volmer)
### bug fix
* Collection input generates `required` attribute if it has `prompt` option. [@nashby](https://github.com/nashby)
## 3.0.1
### bug fix
* Fix XSS vulnerability on label, hint and error components.
## 3.0.0
### enhancements

View File

@ -3,9 +3,9 @@ source 'https://rubygems.org'
gemspec
gem 'country_select', '~> 1.1.1'
gem 'railties', '>= 4.0.0', '< 4.1'
gem 'activemodel', '>= 4.0.0', '< 4.1'
gem 'actionpack', '>= 4.0.0', '< 4.1'
gem 'railties', github: 'rails/rails', branch: '4-0-stable'
gem 'activemodel', github: 'rails/rails', branch: '4-0-stable'
gem 'actionpack', github: 'rails/rails', branch: '4-0-stable'
gem 'rake'
gem 'rdoc'
gem 'tzinfo'

View File

@ -1,60 +1,66 @@
GIT
remote: git://github.com/rails/rails.git
revision: b632a8237745c62747558551c74e32a3bb056e09
branch: 4-0-stable
specs:
actionpack (4.0.2)
activesupport (= 4.0.2)
builder (~> 3.1.0)
erubis (~> 2.7.0)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
activemodel (4.0.2)
activesupport (= 4.0.2)
builder (~> 3.1.0)
activesupport (4.0.2)
i18n (~> 0.6, >= 0.6.4)
minitest (~> 4.2)
multi_json (~> 1.3)
thread_safe (~> 0.1)
tzinfo (~> 0.3.37)
railties (4.0.2)
actionpack (= 4.0.2)
activesupport (= 4.0.2)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
PATH
remote: .
specs:
simple_form (3.0.0)
simple_form (3.0.1)
actionpack (>= 4.0.0, < 4.1)
activemodel (>= 4.0.0, < 4.1)
GEM
remote: https://rubygems.org/
specs:
actionpack (4.0.0)
activesupport (= 4.0.0)
builder (~> 3.1.0)
erubis (~> 2.7.0)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
activemodel (4.0.0)
activesupport (= 4.0.0)
builder (~> 3.1.0)
activesupport (4.0.0)
i18n (~> 0.6, >= 0.6.4)
minitest (~> 4.2)
multi_json (~> 1.3)
thread_safe (~> 0.1)
tzinfo (~> 0.3.37)
atomic (1.1.9)
atomic (1.1.14)
builder (3.1.4)
country_select (1.1.3)
erubis (2.7.0)
i18n (0.6.4)
json (1.7.7)
i18n (0.6.9)
json (1.8.1)
minitest (4.7.5)
multi_json (1.7.7)
multi_json (1.8.2)
rack (1.5.2)
rack-test (0.6.2)
rack (>= 1.0)
railties (4.0.0)
actionpack (= 4.0.0)
activesupport (= 4.0.0)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rake (10.0.4)
rake (10.1.0)
rdoc (4.0.1)
json (~> 1.4)
thor (0.18.1)
thread_safe (0.1.0)
thread_safe (0.1.3)
atomic
tzinfo (0.3.37)
tzinfo (0.3.38)
PLATFORMS
ruby
DEPENDENCIES
actionpack (>= 4.0.0, < 4.1)
activemodel (>= 4.0.0, < 4.1)
actionpack!
activemodel!
country_select (~> 1.1.1)
railties (>= 4.0.0, < 4.1)
railties!
rake
rdoc
simple_form!

View File

@ -500,9 +500,9 @@ specifying the helper method in the column `Mapping` as the `as:` option.
`float` | `input[type=number]` | `float`
`decimal` | `input[type=number]` | `decimal`
`range` | `input[type=range]` | -
`datetime` | `input[type=datetime]` | `datetime/timestamp`
`date` | `input[type=date]` | `date`
`time` | `input[type=time]` | `time`
`datetime` | `datetime select` | `datetime/timestamp`
`date` | `date select` | `date`
`time` | `time select` | `time`
`select` | `select` | `belongs_to`/`has_many`/`has_and_belongs_to_many` associations
`radio_buttons` | collection of `input[type=radio]` | `belongs_to` associations
`check_boxes` | collection of `input[type=checkbox]` | `has_many`/`has_and_belongs_to_many` associations

View File

@ -0,0 +1,11 @@
source 'https://rubygems.org'
gemspec :path => '..'
gem 'country_select', '~> 1.1.1'
gem 'railties', github: 'rails/rails'
gem 'activemodel', github: 'rails/rails'
gem 'actionpack', github: 'rails/rails'
gem 'rake'
gem 'rdoc'
gem 'tzinfo'

View File

@ -142,4 +142,8 @@ SimpleForm.setup do |config|
# Default class for inputs
# config.input_class = nil
# Defines if the default input wrapper class should be included in radio
# collection wrappers.
# config.include_default_input_wrapper_class = true
end

View File

@ -153,6 +153,10 @@ module SimpleForm
mattr_accessor :input_class
@@input_class = nil
# Defines if an input wrapper class should be included or not
mattr_accessor :include_default_input_wrapper_class
@@include_default_input_wrapper_class = true
## WRAPPER CONFIGURATION
# The default wrapper to be used by the FormBuilder.
mattr_accessor :default_wrapper

View File

@ -12,7 +12,7 @@ module SimpleForm
protected
def error_text
"#{options[:error_prefix]} #{errors.send(error_method)}".lstrip.html_safe
"#{html_escape(options[:error_prefix])} #{errors.send(error_method)}".lstrip.html_safe
end
def error_method

View File

@ -5,8 +5,13 @@ module SimpleForm
def hint
@hint ||= begin
hint = options[:hint]
hint_content = hint.is_a?(String) ? hint : translate(:hints)
hint_content.html_safe if hint_content
if hint.is_a?(String)
html_escape(hint)
else
content = translate(:hints)
content.html_safe if content
end
end
end

View File

@ -30,7 +30,7 @@ module SimpleForm
end
def label_text
SimpleForm.label_text.call(raw_label_text, required_label_text).strip.html_safe
SimpleForm.label_text.call(html_escape(raw_label_text), required_label_text).strip.html_safe
end
def label_target

View File

@ -108,16 +108,11 @@ module SimpleForm
#
def input(attribute_name, options={}, &block)
options = @defaults.deep_dup.deep_merge(options) if @defaults
input = find_input(attribute_name, options, &block)
chosen =
if name = options[:wrapper] || find_wrapper_mapping(input.input_type)
name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
else
wrapper
end
input = find_input(attribute_name, options, &block)
wrapper = find_wrapper(input.input_type, options)
chosen.render input
wrapper.render input
end
alias :attribute :input
@ -140,7 +135,11 @@ module SimpleForm
options[:input_html] = options.except(:as, :collection, :label_method, :value_method, *ATTRIBUTE_COMPONENTS)
options = @defaults.deep_dup.deep_merge(options) if @defaults
SimpleForm::Wrappers::Root.new(ATTRIBUTE_COMPONENTS + [:input], wrapper: false).render find_input(attribute_name, options)
input = find_input(attribute_name, options)
wrapper = find_wrapper(input.input_type, options)
components = (wrapper.components & ATTRIBUTE_COMPONENTS) + [:input]
SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
end
# Helper for dealing with association selects/radios, generating the
@ -564,6 +563,14 @@ module SimpleForm
end
end
def find_wrapper(input_type, options)
if name = options[:wrapper] || find_wrapper_mapping(input_type)
name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
else
wrapper
end
end
# If cache_discovery is enabled, use the class level cache that persists
# between requests, otherwise use the instance one.
def discovery_cache #:nodoc:

View File

@ -1,8 +1,11 @@
require 'simple_form/i18n_cache'
require 'active_support/core_ext/string/output_safety'
module SimpleForm
module Inputs
class Base
include ERB::Util
extend I18nCache
include SimpleForm::Helpers::Autofocus

View File

@ -22,7 +22,7 @@ module SimpleForm
options[:item_wrapper_tag] ||= options.fetch(:item_wrapper_tag, SimpleForm.item_wrapper_tag)
options[:item_wrapper_class] = [
item_wrapper_class, options[:item_wrapper_class], SimpleForm.item_wrapper_class
].compact.presence
].compact.presence if SimpleForm.include_default_input_wrapper_class
options[:collection_wrapper_tag] ||= options.fetch(:collection_wrapper_tag, SimpleForm.collection_wrapper_tag)
options[:collection_wrapper_class] = [

View File

@ -2,7 +2,11 @@ module SimpleForm
module Inputs
class DateTimeInput < Base
def input
@builder.send(:"#{input_type}_select", attribute_name, input_options, input_html_options)
if use_html5_inputs?
@builder.send(:"#{input_type}_field", attribute_name, input_html_options)
else
@builder.send(:"#{input_type}_select", attribute_name, input_options, input_html_options)
end
end
private
@ -19,6 +23,10 @@ module SimpleForm
position = ActionView::Helpers::DateTimeSelector::POSITION[position]
"#{attribute_name}_#{position}i"
end
def use_html5_inputs?
input_options[:html5]
end
end
end
end

View File

@ -17,7 +17,7 @@ module SimpleForm
if @options.fetch(:boolean_style, SimpleForm.boolean_style) == :nested
label_options = {}
add_default_name_and_id_for_value(text, label_options)
add_default_name_and_id_for_value(value, label_options)
label_options['for'] = label_options['id']
rendered_item = content_tag(:label, rendered_item, label_options)
end

View File

@ -1,3 +1,3 @@
module SimpleForm
VERSION = "3.0.0".freeze
VERSION = "3.0.1".freeze
end

View File

@ -80,8 +80,13 @@ class ErrorTest < ActionView::TestCase
assert_no_select 'p.error[error_method]'
end
test 'error should generate an error message with raw HTML tags' do
test 'error should escape error prefix text' do
with_error_for @user, :name, error_prefix: '<b>Name</b>'
assert_select 'span.error', "&lt;b&gt;Name&lt;/b&gt; can't be blank"
end
test 'error should generate an error message with raw HTML tags' do
with_error_for @user, :name, error_prefix: '<b>Name</b>'.html_safe
assert_select 'span.error', "Name can't be blank"
assert_select 'span.error b', "Name"
end

View File

@ -43,8 +43,14 @@ class HintTest < ActionView::TestCase
end
test 'hint should be output as html_safe' do
with_hint_for @user, :name, hint: '<b>Bold</b> and not...'
with_hint_for @user, :name, hint: '<b>Bold</b> and not...'.html_safe
assert_select 'span.hint', 'Bold and not...'
assert_select 'span.hint b', 'Bold'
end
test 'builder should escape hint text' do
with_hint_for @user, :name, hint: '<script>alert(1337)</script>'
assert_select 'span.hint', "&lt;script&gt;alert(1337)&lt;/script&gt;"
end
# Without attribute name
@ -132,7 +138,7 @@ class HintTest < ActionView::TestCase
test 'hint with custom wrappers works' do
swap_wrapper do
with_hint_for @user, :name, hint: "can't be blank"
assert_select 'div.omg_hint', "can't be blank"
assert_select 'div.omg_hint', "can&#39;t be blank"
end
end
end

View File

@ -88,14 +88,30 @@ class InputFieldTest < ActionView::TestCase
assert_select 'input[min=18]'
end
test 'builder input_field should use pattern component' do
test 'builder input_field should not use pattern component by default' do
with_concat_form_for(@other_validating_user) do |f|
f.input_field :country, as: :string
end
assert_no_select 'input[pattern="\w+"]'
end
test 'builder input_field should infer pattern from attributes' do
with_concat_form_for(@other_validating_user) do |f|
f.input_field :country, as: :string, pattern: true
end
assert_select 'input[pattern="\w+"]'
end
test 'builder input_field should accept custom patter' do
with_concat_form_for(@other_validating_user) do |f|
f.input_field :country, as: :string, pattern: '\d+'
end
assert_select 'input[pattern="\d+"]'
end
test 'builder input_field should use readonly component' do
with_concat_form_for(@other_validating_user) do |f|
f.input_field :age, as: :integer, readonly: true

View File

@ -29,6 +29,16 @@ class LabelTest < ActionView::TestCase
assert_select 'label.string.required[for=validating_user_name]', /Name/
end
test 'builder should escape label text' do
with_label_for @user, :name, label: '<script>alert(1337)</script>', required: false
assert_select 'label.string', "&lt;script&gt;alert(1337)&lt;/script&gt;"
end
test 'builder should not escape label text if it is safe' do
with_label_for @user, :name, label: '<script>alert(1337)</script>'.html_safe, required: false
assert_select 'label.string script', "alert(1337)"
end
test 'builder should allow passing options to label tag' do
with_label_for @user, :name, label: 'My label', id: 'name_label'
assert_select 'label.string#name_label', /My label/

View File

@ -230,4 +230,21 @@ class CollectionCheckBoxesInputTest < ActionView::TestCase
assert_select 'span.checkbox.inline > label > input'
end
end
test 'input check boxes wrapper class are not included when set to falsey' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_input_for @user, :gender, :check_boxes, collection: [:male, :female]
assert_no_select 'label.checkbox'
end
end
test 'input check boxes custom wrapper class is included when include input wrapper class is falsey' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_input_for @user, :gender, :check_boxes, collection: [:male, :female], item_wrapper_class: 'custom'
assert_no_select 'label.checkbox'
assert_select 'label.custom'
end
end
end

View File

@ -18,6 +18,14 @@ class CollectionRadioButtonsInputTest < ActionView::TestCase
assert_select 'label[for=user_active_false]', 'No'
end
test 'input as radio should generate internal labels with accurate `for` values with nested boolean style' do
swap SimpleForm, boolean_style: :nested do
with_input_for @user, :active, :radio_buttons
assert_select 'label[for=user_active_true]', 'Yes'
assert_select 'label[for=user_active_false]', 'No'
end
end
test 'input as radio should use i18n to translate internal labels' do
store_translations(:en, simple_form: { yes: 'Sim', no: 'Não' }) do
with_input_for @user, :active, :radio_buttons
@ -323,4 +331,21 @@ class CollectionRadioButtonsInputTest < ActionView::TestCase
assert_select 'span.radio.inline > label > input'
end
end
test 'input radio wrapper class are not included when set to falsey' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_input_for @user, :gender, :radio_buttons, collection: [:male, :female]
assert_no_select 'label.radio'
end
end
test 'input check boxes custom wrapper class is included when include input wrapper class is falsey' do
swap SimpleForm, include_default_input_wrapper_class: false, boolean_style: :nested do
with_input_for @user, :gender, :radio_buttons, collection: [:male, :female], item_wrapper_class: 'custom'
assert_no_select 'label.radio'
assert_select 'label.custom'
end
end
end

View File

@ -1,18 +1,69 @@
# encoding: UTF-8
require 'test_helper'
# Tests for all different kinds of inputs.
class DateTimeInputTest < ActionView::TestCase
# DateTime input
test 'input should generate a datetime select by default for datetime attributes' do
# Tests for datetime, date and time inputs when HTML5 compatibility is enabled in the wrapper.
class DateTimeInputWithHtml5Test < ActionView::TestCase
test 'input should generate a datetime input for datetime attributes if HTML5 compatibility is explicitly enbled' do
with_input_for @user, :created_at, :datetime, html5: true
assert_select 'input[type="datetime"]'
end
test 'input should generate a datetime select for datetime attributes' do
with_input_for @user, :created_at, :datetime
1.upto(5) do |i|
assert_select "form select.datetime#user_created_at_#{i}i"
assert_select 'select.datetime'
end
test 'input should generate a date input for date attributes if HTML5 compatibility is explicitly enbled' do
with_input_for @user, :born_at, :date, html5: true
assert_select 'input[type="date"]'
end
test 'input should generate a date select for date attributes' do
with_input_for @user, :born_at, :date
assert_select 'select.date'
end
test 'input should generate a time input for time attributes if HTML5 compatibility is explicitly enbled' do
with_input_for @user, :delivery_time, :time, html5: true
assert_select 'input[type="time"]'
end
test 'input should generate a time select for time attributes' do
with_input_for @user, :delivery_time, :time
assert_select 'select.time'
end
test 'input should generate required html attribute' do
with_input_for @user, :delivery_time, :time, required: true, html5: true
assert_select 'input.required'
assert_select 'input[required]'
end
test 'input should have an aria-required html attribute' do
with_input_for @user, :delivery_time, :time, required: true, html5: true
assert_select 'input[aria-required=true]'
end
end
# Tests for datetime, date and time inputs when HTML5 compatibility is enabled in the wrapper.
class DateTimeInputWithoutHtml5Test < ActionView::TestCase
test 'input should generate a datetime select by default for datetime attributes' do
swap_wrapper do
with_input_for @user, :created_at, :datetime
1.upto(5) do |i|
assert_select "form select.datetime#user_created_at_#{i}i"
end
end
end
test 'input should be able to pass options to datetime select' do
with_input_for @user, :created_at, :datetime,
with_input_for @user, :created_at, :datetime, html5: false,
disabled: true, prompt: { year: 'ano', month: 'mês', day: 'dia' }
assert_select 'select.datetime[disabled=disabled]'
@ -21,16 +72,26 @@ class DateTimeInputTest < ActionView::TestCase
assert_select 'select.datetime option', 'dia'
end
test 'input should generate a datetime input for datetime attributes if HTML5 compatibility is explicitly enabled' do
swap_wrapper do
with_input_for @user, :created_at, :datetime, html5: true
assert_select 'input[type="datetime"]'
end
end
test 'input should generate a date select for date attributes' do
with_input_for @user, :born_at, :date
assert_select 'select.date#user_born_at_1i'
assert_select 'select.date#user_born_at_2i'
assert_select 'select.date#user_born_at_3i'
assert_no_select 'select.date#user_born_at_4i'
swap_wrapper do
with_input_for @user, :born_at, :date
assert_select 'select.date#user_born_at_1i'
assert_select 'select.date#user_born_at_2i'
assert_select 'select.date#user_born_at_3i'
assert_no_select 'select.date#user_born_at_4i'
end
end
test 'input should be able to pass options to date select' do
with_input_for @user, :born_at, :date, as: :date,
with_input_for @user, :born_at, :date, as: :date, html5: false,
disabled: true, prompt: { year: 'ano', month: 'mês', day: 'dia' }
assert_select 'select.date[disabled=disabled]'
@ -40,21 +101,31 @@ class DateTimeInputTest < ActionView::TestCase
end
test 'input should be able to pass :default to date select' do
with_input_for @user, :born_at, :date, default: Date.today
with_input_for @user, :born_at, :date, default: Date.today, html5: false
assert_select "select.date option[value=#{Date.today.year}][selected=selected]"
end
test 'input should generate a date input for date attributes if HTML5 compatibility is explicitly enabled' do
swap_wrapper do
with_input_for @user, :born_at, :date, html5: true
assert_select 'input[type="date"]'
end
end
test 'input should generate a time select for time attributes' do
with_input_for @user, :delivery_time, :time
assert_select 'input[type=hidden]#user_delivery_time_1i'
assert_select 'input[type=hidden]#user_delivery_time_2i'
assert_select 'input[type=hidden]#user_delivery_time_3i'
assert_select 'select.time#user_delivery_time_4i'
assert_select 'select.time#user_delivery_time_5i'
swap_wrapper do
with_input_for @user, :delivery_time, :time
assert_select 'input[type=hidden]#user_delivery_time_1i'
assert_select 'input[type=hidden]#user_delivery_time_2i'
assert_select 'input[type=hidden]#user_delivery_time_3i'
assert_select 'select.time#user_delivery_time_4i'
assert_select 'select.time#user_delivery_time_5i'
end
end
test 'input should be able to pass options to time select' do
with_input_for @user, :delivery_time, :time, required: true,
with_input_for @user, :delivery_time, :time, required: true, html5: false,
disabled: true, prompt: { hour: 'hora', minute: 'minuto' }
assert_select 'select.time[disabled=disabled]'
@ -62,44 +133,40 @@ class DateTimeInputTest < ActionView::TestCase
assert_select 'select.time option', 'minuto'
end
test 'input should generate a time input for time attributes if HTML5 compatibility is explicitly enabled' do
swap_wrapper do
with_input_for @user, :delivery_time, :time, html5: true
assert_select 'input[type="time"]'
end
end
test 'label should use i18n to get target for date input type' do
store_translations(:en, date: { order: ['month', 'day', 'year'] }) do
with_input_for :project, :created_at, :date
with_input_for :project, :created_at, :date, html5: false
assert_select 'label[for=project_created_at_2i]'
end
end
test 'label should use i18n to get target for datetime input type' do
store_translations(:en, date: { order: ['month', 'day', 'year'] }) do
with_input_for :project, :created_at, :datetime
with_input_for :project, :created_at, :datetime, html5: false
assert_select 'label[for=project_created_at_2i]'
end
end
test 'label should use order to get target when date input type' do
with_input_for :project, :created_at, :date, order: ['month', 'year', 'day']
with_input_for :project, :created_at, :date, order: ['month', 'year', 'day'], html5: false
assert_select 'label[for=project_created_at_2i]'
end
test 'label should use order to get target when datetime input type' do
with_input_for :project, :created_at, :datetime, order: ['month', 'year', 'day']
with_input_for :project, :created_at, :datetime, order: ['month', 'year', 'day'], html5: false
assert_select 'label[for=project_created_at_2i]'
end
test 'label should point to first option when time input type' do
with_input_for :project, :created_at, :time
with_input_for :project, :created_at, :time, html5: false
assert_select 'label[for=project_created_at_4i]'
end
test 'date time input should generate required html attribute' do
with_input_for @user, :delivery_time, :time, required: true
assert_select 'select.required'
assert_select 'select[required]'
end
test 'date time input has an aria-required html attribute' do
with_input_for @user, :delivery_time, :time, required: true
assert_select 'select.required'
assert_select 'select[aria-required=true]'
end
end