formtastic 2.0.0.rc3 → 2.0.0.rc4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. data/.travis.yml +7 -0
  2. data/CHANGELOG +26 -0
  3. data/Gemfile +2 -0
  4. data/README.textile +0 -1
  5. data/RELEASE_PROCESS +0 -1
  6. data/Rakefile +1 -1
  7. data/app/assets/stylesheets/formtastic.css +3 -3
  8. data/app/assets/stylesheets/formtastic_ie6.css +7 -1
  9. data/app/assets/stylesheets/formtastic_ie7.css +7 -1
  10. data/formtastic.gemspec +0 -15
  11. data/lib/formtastic/form_builder.rb +2 -0
  12. data/lib/formtastic/helpers/errors_helper.rb +22 -0
  13. data/lib/formtastic/helpers/form_helper.rb +18 -16
  14. data/lib/formtastic/helpers/input_helper.rb +9 -7
  15. data/lib/formtastic/helpers/inputs_helper.rb +11 -3
  16. data/lib/formtastic/helpers/reflection.rb +5 -1
  17. data/lib/formtastic/inputs/base/collections.rb +7 -0
  18. data/lib/formtastic/inputs/base/html.rb +1 -1
  19. data/lib/formtastic/inputs/base/timeish.rb +7 -2
  20. data/lib/formtastic/inputs/base/validations.rb +39 -8
  21. data/lib/formtastic/inputs/check_boxes_input.rb +3 -3
  22. data/lib/formtastic/inputs/file_input.rb +4 -4
  23. data/lib/formtastic/inputs/number_input.rb +1 -1
  24. data/lib/formtastic/inputs/radio_input.rb +1 -1
  25. data/lib/formtastic/inputs/time_input.rb +25 -3
  26. data/lib/formtastic/version.rb +1 -1
  27. data/lib/generators/templates/formtastic.rb +10 -1
  28. data/spec/builder/errors_spec.rb +10 -0
  29. data/spec/builder/semantic_fields_for_spec.rb +77 -36
  30. data/spec/helpers/form_helper_spec.rb +32 -0
  31. data/spec/helpers/input_helper_spec.rb +196 -102
  32. data/spec/helpers/inputs_helper_spec.rb +85 -73
  33. data/spec/helpers/reflection_helper_spec.rb +32 -0
  34. data/spec/inputs/check_boxes_input_spec.rb +21 -6
  35. data/spec/inputs/date_input_spec.rb +20 -0
  36. data/spec/inputs/datetime_input_spec.rb +30 -11
  37. data/spec/inputs/label_spec.rb +8 -0
  38. data/spec/inputs/number_input_spec.rb +298 -2
  39. data/spec/inputs/radio_input_spec.rb +5 -6
  40. data/spec/inputs/string_input_spec.rb +22 -5
  41. data/spec/inputs/time_input_spec.rb +51 -7
  42. data/spec/spec_helper.rb +64 -12
  43. metadata +11 -9
@@ -127,7 +127,7 @@ module Formtastic
127
127
  value = choice_value(choice)
128
128
  builder.check_box(
129
129
  association_primary_key || method,
130
- input_html_options.merge(:id => choice_input_dom_id(choice), :name => input_name, :disabled => disabled?(value)),
130
+ input_html_options.merge(:id => choice_input_dom_id(choice), :name => input_name, :disabled => disabled?(value), :required => false),
131
131
  value,
132
132
  unchecked_value
133
133
  )
@@ -139,7 +139,7 @@ module Formtastic
139
139
  input_name,
140
140
  value,
141
141
  checked?(value),
142
- input_html_options.merge(:id => choice_input_dom_id(choice), :disabled => disabled?(value))
142
+ input_html_options.merge(:id => choice_input_dom_id(choice), :disabled => disabled?(value), :required => false)
143
143
  )
144
144
  end
145
145
 
@@ -154,7 +154,7 @@ module Formtastic
154
154
  def selected_values
155
155
  if object.respond_to?(method)
156
156
  selected_items = [object.send(method)].compact.flatten
157
- [*selected_items.map { |o| send_or_call(value_method, o) }].compact
157
+ [*selected_items.map { |o| send_or_call_or_object(value_method, o) }].compact
158
158
  else
159
159
  []
160
160
  end
@@ -13,7 +13,7 @@ module Formtastic
13
13
  #
14
14
  # <%= semantic_form_for(@user, :html => { :multipart => true }) do |f| %>
15
15
  # <%= f.inputs do %>
16
- # <%= f.input :email_address, :as => :email %>
16
+ # <%= f.input :avatar, :as => :file %>
17
17
  # <% end %>
18
18
  # <% end %>
19
19
  #
@@ -21,8 +21,8 @@ module Formtastic
21
21
  # <fieldset>
22
22
  # <ol>
23
23
  # <li class="email">
24
- # <label for="user_email_address">Email address</label>
25
- # <input type="email" id="user_email_address" name="user[email_address]">
24
+ # <label for="user_avatar">Avatar</label>
25
+ # <input type="file" id="user_avatar" name="user[avatar]">
26
26
  # </li>
27
27
  # </ol>
28
28
  # </fieldset>
@@ -39,4 +39,4 @@ module Formtastic
39
39
  end
40
40
  end
41
41
  end
42
- end
42
+ end
@@ -96,7 +96,7 @@ module Formtastic
96
96
  return options[:step] if options.key?(:step)
97
97
  return validation_step if validation_step
98
98
  return 1 if validation_integer_only?
99
- 1
99
+ "any"
100
100
  end
101
101
 
102
102
  def min_option
@@ -140,7 +140,7 @@ module Formtastic
140
140
 
141
141
  def choice_html(choice)
142
142
  template.content_tag(:label,
143
- builder.radio_button(input_name, choice_value(choice), input_html_options.merge(choice_html_options(choice))) <<
143
+ builder.radio_button(input_name, choice_value(choice), input_html_options.merge(choice_html_options(choice)).merge(:required => false)) <<
144
144
  choice_label(choice),
145
145
  label_html_options.merge(:for => choice_input_dom_id(choice), :class => nil)
146
146
  )
@@ -1,16 +1,38 @@
1
1
  module Formtastic
2
2
  module Inputs
3
3
  # Outputs a series of select boxes for the fragments that make up a time (hour, minute, second).
4
+ # Unless `:ignore_date` is true, it will render hidden inputs for the year, month and day as
5
+ # well, defaulting to `Time.current` if the form object doesn't have a value, much like Rails'
6
+ # own `time_select`.
4
7
  #
5
8
  # @see Formtastic::Inputs::Timeish Timeish module for documetation of date, time and datetime input options.
6
9
  class TimeInput
7
10
  include Base
8
11
  include Base::Timeish
9
12
 
10
- # we don't want year / month / day fragments in a time select
11
- def date_fragments
12
- []
13
+ # we don't want year / month / day fragments if :ignore_date => true
14
+ def fragments
15
+ time_fragments
13
16
  end
17
+
18
+ def value_or_default_value
19
+ value ? value : Time.current
20
+ end
21
+
22
+ def fragment_value(fragment)
23
+ value_or_default_value.send(fragment)
24
+ end
25
+
26
+ def hidden_fragments
27
+ if !options[:ignore_date]
28
+ date_fragments.map do |fragment|
29
+ template.hidden_field_tag("#{object_name}[#{fragment_name(fragment)}]", fragment_value(fragment), :id => fragment_id(fragment), :disabled => input_html_options[:disabled] )
30
+ end.join.html_safe
31
+ else
32
+ super
33
+ end
34
+ end
35
+
14
36
  end
15
37
  end
16
38
  end
@@ -1,3 +1,3 @@
1
1
  module Formtastic
2
- VERSION = "2.0.0.rc3"
2
+ VERSION = "2.0.0.rc4"
3
3
  end
@@ -66,8 +66,17 @@
66
66
  # Specifies if labels/hints for input fields automatically be looked up using I18n.
67
67
  # Default value: true. Overridden for specific fields by setting value to true,
68
68
  # i.e. :label => true, or :hint => true (or opposite depending on initialized value)
69
- # Formtastic::FormBuilder_lookups_by_default = false
69
+ # Formtastic::FormBuilder.i18n_lookups_by_default = false
70
70
 
71
71
  # You can add custom inputs or override parts of Formtastic by subclassing Formtastic::FormBuilder and
72
72
  # specifying that class here. Defaults to Formtastic::FormBuilder.
73
73
  # Formtastic::Helpers::FormHelper.builder = MyCustomBuilder
74
+
75
+ # You can opt out of Formtastic's use of the HTML5 `required` attribute on `<input>`, `<select>`
76
+ # and `<textarea>` tags by setting this to false (defaults to true).
77
+ # Formtastic::FormBuilder.use_required_attribute = false
78
+
79
+ # You can opt out of new HTML5 browser validations (for things like email and url inputs) by setting
80
+ # this to false. Doing so will add a `novalidate` attribute to the `<form>` tag.
81
+ # See http://diveintohtml5.org/forms.html#validation for more info.
82
+ # Formtastic::FormBuilder.perform_browser_validations = true
@@ -107,6 +107,16 @@ describe 'Formtastic::FormBuilder#errors_on' do
107
107
  output_buffer.should_not have_tag('p.inine-errors')
108
108
  output_buffer.should_not have_tag('ul.errors')
109
109
  end
110
+
111
+ it 'should allow calling deprecated errors_on and inline_errors_for helpers' do
112
+ Formtastic::FormBuilder.inline_errors = :sentence
113
+ with_deprecation_silenced do
114
+ concat(semantic_form_for(@new_post) do |builder|
115
+ builder.errors_on :title
116
+ builder.inline_errors_for :title
117
+ end)
118
+ end
119
+ end
110
120
 
111
121
  end
112
122
 
@@ -11,51 +11,93 @@ describe 'Formtastic::FormBuilder#fields_for' do
11
11
  @new_post.stub!(:author).and_return(::Author.new)
12
12
  end
13
13
 
14
- it 'yields an instance of FormHelper.builder' do
15
- semantic_form_for(@new_post) do |builder|
16
- builder.fields_for(:author) do |nested_builder|
14
+ context 'outside a form_for block' do
15
+ it 'yields an instance of FormHelper.builder' do
16
+ semantic_fields_for(@new_post) do |nested_builder|
17
+ nested_builder.class.should == Formtastic::Helpers::FormHelper.builder
18
+ end
19
+ semantic_fields_for(@new_post.author) do |nested_builder|
20
+ nested_builder.class.should == Formtastic::Helpers::FormHelper.builder
21
+ end
22
+ semantic_fields_for(:author, @new_post.author) do |nested_builder|
23
+ nested_builder.class.should == Formtastic::Helpers::FormHelper.builder
24
+ end
25
+ semantic_fields_for(:author, @hash_backed_author) do |nested_builder|
17
26
  nested_builder.class.should == Formtastic::Helpers::FormHelper.builder
18
27
  end
19
28
  end
20
- end
21
-
22
- it 'nests the object name' do
23
- semantic_form_for(@new_post) do |builder|
24
- builder.fields_for(@bob) do |nested_builder|
25
- nested_builder.object_name.should == 'post[author]'
29
+
30
+ it 'should respond to input' do
31
+ semantic_fields_for(@new_post) do |nested_builder|
32
+ nested_builder.respond_to?(:input).should be_true
33
+ end
34
+ semantic_fields_for(@new_post.author) do |nested_builder|
35
+ nested_builder.respond_to?(:input).should be_true
36
+ end
37
+ semantic_fields_for(:author, @new_post.author) do |nested_builder|
38
+ nested_builder.respond_to?(:input).should be_true
39
+ end
40
+ semantic_fields_for(:author, @hash_backed_author) do |nested_builder|
41
+ nested_builder.respond_to?(:input).should be_true
26
42
  end
27
43
  end
28
44
  end
29
45
 
30
- it 'supports passing collection as second parameter' do
31
- semantic_form_for(@new_post) do |builder|
32
- builder.semantic_fields_for(:author, [@fred,@bob]) do |nested_builder|
33
- nested_builder.object_name.should =~ /post\[author_attributes\]\[\d+\]/
46
+ context 'within a form_for block' do
47
+ it 'yields an instance of FormHelper.builder' do
48
+ semantic_form_for(@new_post) do |builder|
49
+ builder.semantic_fields_for(:author) do |nested_builder|
50
+ nested_builder.class.should == Formtastic::Helpers::FormHelper.builder
51
+ end
34
52
  end
35
53
  end
36
- end
37
-
38
- it 'should sanitize html id for li tag' do
39
- @bob.stub!(:column_for_attribute).and_return(mock('column', :type => :string, :limit => 255))
40
- concat(semantic_form_for(@new_post) do |builder|
41
- concat(builder.fields_for(@bob, :index => 1) do |nested_builder|
42
- concat(nested_builder.inputs(:login))
54
+
55
+ it 'yields an instance of FormHelper.builder with hash-like model' do
56
+ semantic_form_for(:user) do |builder|
57
+ builder.semantic_fields_for(:author, @hash_backed_author) do |nested_builder|
58
+ nested_builder.class.should == Formtastic::Helpers::FormHelper.builder
59
+ end
60
+ end
61
+ end
62
+
63
+ it 'nests the object name' do
64
+ semantic_form_for(@new_post) do |builder|
65
+ builder.semantic_fields_for(@bob) do |nested_builder|
66
+ nested_builder.object_name.should == 'post[author]'
67
+ end
68
+ end
69
+ end
70
+
71
+ it 'supports passing collection as second parameter' do
72
+ semantic_form_for(@new_post) do |builder|
73
+ builder.semantic_fields_for(:author, [@fred,@bob]) do |nested_builder|
74
+ nested_builder.object_name.should =~ /post\[author_attributes\]\[\d+\]/
75
+ end
76
+ end
77
+ end
78
+
79
+ it 'should sanitize html id for li tag' do
80
+ @bob.stub!(:column_for_attribute).and_return(mock('column', :type => :string, :limit => 255))
81
+ concat(semantic_form_for(@new_post) do |builder|
82
+ concat(builder.semantic_fields_for(@bob, :index => 1) do |nested_builder|
83
+ concat(nested_builder.inputs(:login))
84
+ end)
43
85
  end)
44
- end)
45
- output_buffer.should have_tag('form fieldset.inputs #post_author_1_login_input')
46
- # Not valid selector, so using good ol' regex
47
- output_buffer.should_not =~ /id="post\[author\]_1_login_input"/
48
- # <=> output_buffer.should_not have_tag('form fieldset.inputs #post[author]_1_login_input')
49
- end
50
-
51
- it 'should use namespace provided in nested fields' do
52
- @bob.stub!(:column_for_attribute).and_return(mock('column', :type => :string, :limit => 255))
53
- concat(semantic_form_for(@new_post, :namespace => 'context2') do |builder|
54
- concat(builder.fields_for(@bob, :index => 1) do |nested_builder|
55
- concat(nested_builder.inputs(:login))
86
+ output_buffer.should have_tag('form fieldset.inputs #post_author_1_login_input')
87
+ # Not valid selector, so using good ol' regex
88
+ output_buffer.should_not =~ /id="post\[author\]_1_login_input"/
89
+ # <=> output_buffer.should_not have_tag('form fieldset.inputs #post[author]_1_login_input')
90
+ end
91
+
92
+ it 'should use namespace provided in nested fields' do
93
+ @bob.stub!(:column_for_attribute).and_return(mock('column', :type => :string, :limit => 255))
94
+ concat(semantic_form_for(@new_post, :namespace => 'context2') do |builder|
95
+ concat(builder.semantic_fields_for(@bob, :index => 1) do |nested_builder|
96
+ concat(nested_builder.inputs(:login))
97
+ end)
56
98
  end)
57
- end)
58
- output_buffer.should have_tag('form fieldset.inputs #context2_post_author_1_login_input')
99
+ output_buffer.should have_tag('form fieldset.inputs #context2_post_author_1_login_input')
100
+ end
59
101
  end
60
102
 
61
103
  context "when I rendered my own hidden id input" do
@@ -68,7 +110,7 @@ describe 'Formtastic::FormBuilder#fields_for' do
68
110
  @fred.stub!(:posts_attributes=)
69
111
 
70
112
  concat(semantic_form_for(@fred) do |builder|
71
- concat(builder.fields_for(:posts) do |nested_builder|
113
+ concat(builder.semantic_fields_for(:posts) do |nested_builder|
72
114
  concat(nested_builder.input(:id, :as => :hidden))
73
115
  concat(nested_builder.input(:title))
74
116
  end)
@@ -85,4 +127,3 @@ describe 'Formtastic::FormBuilder#fields_for' do
85
127
  end
86
128
 
87
129
  end
88
-
@@ -23,6 +23,38 @@ describe 'FormHelper' do
23
23
  end)
24
24
  output_buffer.should have_tag("form.formtastic")
25
25
  end
26
+
27
+ it 'adds a "novalidate" attribute to the generated form when configured to do so' do
28
+ with_config :perform_browser_validations, true do
29
+ concat(semantic_form_for(@new_post, :url => '/hello') do |builder|
30
+ end)
31
+ output_buffer.should_not have_tag("form[@novalidate]")
32
+ end
33
+ end
34
+
35
+ it 'omits a "novalidate" attribute to the generated form when configured to do so' do
36
+ with_config :perform_browser_validations, false do
37
+ concat(semantic_form_for(@new_post, :url => '/hello') do |builder|
38
+ end)
39
+ output_buffer.should have_tag("form[@novalidate]")
40
+ end
41
+ end
42
+
43
+ it 'allows form HTML to override "novalidate" attribute when configure to not show' do
44
+ with_config :perform_browser_validations, false do
45
+ concat(semantic_form_for(@new_post, :url => '/hello', :html => { :novalidate => true }) do |builder|
46
+ end)
47
+ output_buffer.should have_tag("form[@novalidate]")
48
+ end
49
+ end
50
+
51
+ it 'allows form HTML to override "novalidate" attribute when configure to show' do
52
+ with_config :perform_browser_validations, true do
53
+ concat(semantic_form_for(@new_post, :url => '/hello', :html => { :novalidate => false }) do |builder|
54
+ end)
55
+ output_buffer.should_not have_tag("form[@novalidate]")
56
+ end
57
+ end
26
58
 
27
59
  it 'adds a class of "xyz" to the generated form' do
28
60
  Formtastic::Helpers::FormHelper.default_form_class = 'xyz'
@@ -8,18 +8,18 @@ describe 'Formtastic::FormBuilder#input' do
8
8
  before do
9
9
  @output_buffer = ''
10
10
  mock_everything
11
-
11
+
12
12
  @errors = mock('errors')
13
13
  @errors.stub!(:[]).and_return([])
14
14
  @new_post.stub!(:errors).and_return(@errors)
15
15
  end
16
-
16
+
17
17
  after do
18
18
  ::I18n.backend.reload!
19
19
  end
20
-
20
+
21
21
  describe 'arguments and options' do
22
-
22
+
23
23
  it 'should require the first argument (the method on form\'s object)' do
24
24
  lambda {
25
25
  concat(semantic_form_for(@new_post) do |builder|
@@ -31,7 +31,7 @@ describe 'Formtastic::FormBuilder#input' do
31
31
  describe ':required option' do
32
32
 
33
33
  describe 'when true' do
34
-
34
+
35
35
  it 'should set a "required" class' do
36
36
  with_config :required_string, " required yo!" do
37
37
  concat(semantic_form_for(@new_post) do |builder|
@@ -114,21 +114,21 @@ describe 'Formtastic::FormBuilder#input' do
114
114
  before do
115
115
  @new_post.stub!(:class).and_return(::PostModel)
116
116
  end
117
-
117
+
118
118
  after do
119
119
  @new_post.stub!(:class).and_return(::Post)
120
120
  end
121
121
  describe 'and validates_presence_of was called for the method' do
122
122
  it 'should be required' do
123
-
123
+
124
124
  @new_post.class.should_receive(:validators_on).with(:title).any_number_of_times.and_return([
125
125
  active_model_presence_validator([:title])
126
126
  ])
127
-
127
+
128
128
  @new_post.class.should_receive(:validators_on).with(:body).any_number_of_times.and_return([
129
129
  active_model_presence_validator([:body], {:if => true})
130
130
  ])
131
-
131
+
132
132
  concat(semantic_form_for(@new_post) do |builder|
133
133
  concat(builder.input(:title))
134
134
  concat(builder.input(:body))
@@ -136,84 +136,169 @@ describe 'Formtastic::FormBuilder#input' do
136
136
  output_buffer.should have_tag('form li.required')
137
137
  output_buffer.should_not have_tag('form li.optional')
138
138
  end
139
-
139
+
140
+ it 'should be required when there is :on => :create option on create' do
141
+ with_config :required_string, " required yo!" do
142
+ @new_post.class.should_receive(:validators_on).with(:title).any_number_of_times.and_return([
143
+ active_model_presence_validator([:title], {:on => :create})
144
+ ])
145
+ concat(semantic_form_for(@new_post) do |builder|
146
+ concat(builder.input(:title))
147
+ end)
148
+ output_buffer.should have_tag('form li.required')
149
+ output_buffer.should_not have_tag('form li.optional')
150
+ end
151
+ end
152
+
153
+ it 'should be required when there is :on => :save option on create' do
154
+ with_config :required_string, " required yo!" do
155
+ @new_post.class.should_receive(:validators_on).with(:title).any_number_of_times.and_return([
156
+ active_model_presence_validator([:title], {:on => :save})
157
+ ])
158
+ concat(semantic_form_for(@new_post) do |builder|
159
+ concat(builder.input(:title))
160
+ end)
161
+ output_buffer.should have_tag('form li.required')
162
+ output_buffer.should_not have_tag('form li.optional')
163
+ end
164
+ end
165
+
166
+ it 'should be required when there is :on => :save option on update' do
167
+ with_config :required_string, " required yo!" do
168
+ @fred.class.should_receive(:validators_on).with(:login).any_number_of_times.and_return([
169
+ active_model_presence_validator([:login], {:on => :save})
170
+ ])
171
+ concat(semantic_form_for(@fred) do |builder|
172
+ concat(builder.input(:login))
173
+ end)
174
+ output_buffer.should have_tag('form li.required')
175
+ output_buffer.should_not have_tag('form li.optional')
176
+ end
177
+ end
178
+
179
+ it 'should not be required when there is :on => :create option on update' do
180
+ @fred.class.should_receive(:validators_on).with(:login).any_number_of_times.and_return([
181
+ active_model_presence_validator([:login], {:on => :create})
182
+ ])
183
+ concat(semantic_form_for(@fred) do |builder|
184
+ concat(builder.input(:login))
185
+ end)
186
+ output_buffer.should_not have_tag('form li.required')
187
+ output_buffer.should have_tag('form li.optional')
188
+ end
189
+
190
+ it 'should not be required when there is :on => :update option on create' do
191
+ @new_post.class.should_receive(:validators_on).with(:title).any_number_of_times.and_return([
192
+ active_model_presence_validator([:title], {:on => :update})
193
+ ])
194
+ concat(semantic_form_for(@new_post) do |builder|
195
+ concat(builder.input(:title))
196
+ end)
197
+ output_buffer.should_not have_tag('form li.required')
198
+ output_buffer.should have_tag('form li.optional')
199
+ end
200
+
140
201
  it 'should be not be required if the optional :if condition is not satisifed' do
141
- should_be_required(:required => false, :options => { :if => false })
202
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => false })
142
203
  end
143
-
204
+
144
205
  it 'should not be required if the optional :if proc evaluates to false' do
145
- should_be_required(:required => false, :options => { :if => proc { |record| false } })
206
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => proc { |record| false } })
146
207
  end
147
-
208
+
148
209
  it 'should be required if the optional :if proc evaluates to true' do
149
- should_be_required(:required => true, :options => { :if => proc { |record| true } })
210
+ presence_should_be_required(:required => true, :tag => :body, :options => { :if => proc { |record| true } })
150
211
  end
151
-
212
+
152
213
  it 'should not be required if the optional :unless proc evaluates to true' do
153
- should_be_required(:required => false, :options => { :unless => proc { |record| true } })
214
+ presence_should_be_required(:required => false, :tag => :body, :options => { :unless => proc { |record| true } })
154
215
  end
155
-
216
+
156
217
  it 'should be required if the optional :unless proc evaluates to false' do
157
- should_be_required(:required => true, :options => { :unless => proc { |record| false } })
218
+ presence_should_be_required(:required => true, :tag => :body, :options => { :unless => proc { |record| false } })
158
219
  end
159
-
220
+
160
221
  it 'should be required if the optional :if with a method string evaluates to true' do
161
222
  @new_post.should_receive(:required_condition).and_return(true)
162
- should_be_required(:required => true, :options => { :if => :required_condition })
223
+ presence_should_be_required(:required => true, :tag => :body, :options => { :if => :required_condition })
163
224
  end
164
-
225
+
165
226
  it 'should be required if the optional :if with a method string evaluates to false' do
166
227
  @new_post.should_receive(:required_condition).and_return(false)
167
- should_be_required(:required => false, :options => { :if => :required_condition })
228
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => :required_condition })
168
229
  end
169
-
170
- it 'should not be required if the optional :unless with a method string evaluates to false' do
230
+
231
+ it 'should be required if the optional :unless with a method string evaluates to false' do
171
232
  @new_post.should_receive(:required_condition).and_return(false)
172
- should_be_required(:required => true, :options => { :unless => :required_condition })
233
+ presence_should_be_required(:required => true, :tag => :body, :options => { :unless => :required_condition })
173
234
  end
174
-
175
- it 'should be required if the optional :unless with a method string evaluates to true' do
235
+
236
+ it 'should not be required if the optional :unless with a method string evaluates to true' do
176
237
  @new_post.should_receive(:required_condition).and_return(true)
177
- should_be_required(:required => false, :options => { :unless => :required_condition })
238
+ presence_should_be_required(:required => false, :tag => :body, :options => { :unless => :required_condition })
178
239
  end
179
240
  end
180
-
241
+
181
242
  describe 'and validates_inclusion_of was called for the method' do
182
243
  it 'should be required' do
183
244
  @new_post.class.should_receive(:validators_on).with(:published).any_number_of_times.and_return([
184
245
  active_model_inclusion_validator([:published], {:in => [false, true]})
185
246
  ])
186
-
187
- concat(semantic_form_for(@new_post) do |builder|
188
- concat(builder.input(:published))
189
- end)
190
- output_buffer.should have_tag('form li.required')
191
- output_buffer.should_not have_tag('form li.optional')
247
+ should_be_required(:tag => :published, :required => true)
192
248
  end
193
-
249
+
194
250
  it 'should not be required if allow_blank is true' do
195
251
  @new_post.class.should_receive(:validators_on).with(:published).any_number_of_times.and_return([
196
252
  active_model_inclusion_validator([:published], {:in => [false, true], :allow_blank => true})
197
253
  ])
198
-
199
- concat(semantic_form_for(@new_post) do |builder|
200
- concat(builder.input(:published))
201
- end)
202
- output_buffer.should_not have_tag('form li.required')
203
- output_buffer.should have_tag('form li.optional')
254
+ should_be_required(:tag => :published, :required => false)
204
255
  end
205
256
  end
206
-
257
+
258
+ describe 'and validates_length_of was called for the method' do
259
+ it 'should be required if minimum is set' do
260
+ length_should_be_required(:tag => :title, :required => true, :options => {:minimum => 1})
261
+ end
262
+
263
+ it 'should be required if :within is set' do
264
+ length_should_be_required(:tag => :title, :required => true, :options => {:within => 1..5})
265
+ end
266
+
267
+ it 'should not be required if :within allows zero length' do
268
+ length_should_be_required(:tag => :title, :required => false, :options => {:within => 0..5})
269
+ end
270
+
271
+ it 'should not be required if only :minimum is zero' do
272
+ length_should_be_required(:tag => :title, :required => false, :options => {:minimum => 0})
273
+ end
274
+
275
+ it 'should not be required if only :minimum is not set' do
276
+ length_should_be_required(:tag => :title, :required => false, :options => {:maximum => 5})
277
+ end
278
+
279
+ it 'should not be required if allow_blank is true' do
280
+ length_should_be_required(:tag => :published, :required => false, :options => {:allow_blank => true})
281
+ end
282
+ end
283
+
284
+ def add_presence_validator(options)
285
+ @new_post.class.stub!(:validators_on).with(options[:tag]).and_return([
286
+ active_model_presence_validator([options[:tag]], options[:options])
287
+ ])
288
+ end
289
+
290
+ def add_length_validator(options)
291
+ @new_post.class.should_receive(:validators_on).with(options[:tag]).any_number_of_times {[
292
+ active_model_length_validator([options[:tag]], options[:options])
293
+ ]}
294
+ end
295
+
207
296
  # TODO make a matcher for this?
208
297
  def should_be_required(options)
209
- @new_post.class.stub!(:validators_on).with(:body).and_return([
210
- active_model_presence_validator([:body], options[:options])
211
- ])
212
-
213
298
  concat(semantic_form_for(@new_post) do |builder|
214
- concat(builder.input(:body))
299
+ concat(builder.input(options[:tag]))
215
300
  end)
216
-
301
+
217
302
  if options[:required]
218
303
  output_buffer.should_not have_tag('form li.optional')
219
304
  output_buffer.should have_tag('form li.required')
@@ -222,24 +307,34 @@ describe 'Formtastic::FormBuilder#input' do
222
307
  output_buffer.should_not have_tag('form li.required')
223
308
  end
224
309
  end
225
-
310
+
311
+ def presence_should_be_required(options)
312
+ add_presence_validator(options)
313
+ should_be_required(options)
314
+ end
315
+
316
+ def length_should_be_required(options)
317
+ add_length_validator(options)
318
+ should_be_required(options)
319
+ end
320
+
226
321
  # TODO JF reversed this during refactor, need to make sure
227
322
  describe 'and there are no requirement validations on the method' do
228
323
  before do
229
324
  @new_post.class.should_receive(:validators_on).with(:title).and_return([])
230
325
  end
231
-
232
- it 'should use the default value' do
326
+
327
+ it 'should not be required' do
233
328
  concat(semantic_form_for(@new_post) do |builder|
234
329
  concat(builder.input(:title))
235
330
  end)
236
- output_buffer.should have_tag('form li.required')
237
- output_buffer.should_not have_tag('form li.optional')
331
+ output_buffer.should_not have_tag('form li.required')
332
+ output_buffer.should have_tag('form li.optional')
238
333
  end
239
334
  end
240
-
335
+
241
336
  end
242
-
337
+
243
338
  describe 'and an object without :validators_on' do
244
339
 
245
340
  it 'should use the default value' do
@@ -304,99 +399,98 @@ describe 'Formtastic::FormBuilder#input' do
304
399
  @new_post.stub!(:column_for_attribute).with(:aws_instance_id).and_return(mock('column', :type => :integer))
305
400
  default_input_type(:integer, :aws_instance_id).should == :number
306
401
  end
307
-
402
+
308
403
  it 'should default to :select for associations' do
309
404
  @new_post.class.stub!(:reflect_on_association).with(:user_id).and_return(mock('ActiveRecord::Reflection::AssociationReflection'))
310
405
  @new_post.class.stub!(:reflect_on_association).with(:section_id).and_return(mock('ActiveRecord::Reflection::AssociationReflection'))
311
406
  default_input_type(:integer, :user_id).should == :select
312
407
  default_input_type(:integer, :section_id).should == :select
313
408
  end
314
-
409
+
315
410
  it 'should default to :password for :string column types with "password" in the method name' do
316
411
  default_input_type(:string, :password).should == :password
317
412
  default_input_type(:string, :hashed_password).should == :password
318
413
  default_input_type(:string, :password_hash).should == :password
319
414
  end
320
-
415
+
321
416
  it 'should default to :text for :text column types' do
322
417
  default_input_type(:text).should == :text
323
418
  end
324
-
419
+
325
420
  it 'should default to :date for :date column types' do
326
421
  default_input_type(:date).should == :date
327
422
  end
328
-
423
+
329
424
  it 'should default to :datetime for :datetime and :timestamp column types' do
330
425
  default_input_type(:datetime).should == :datetime
331
426
  default_input_type(:timestamp).should == :datetime
332
427
  end
333
-
428
+
334
429
  it 'should default to :time for :time column types' do
335
430
  default_input_type(:time).should == :time
336
431
  end
337
-
432
+
338
433
  it 'should default to :boolean for :boolean column types' do
339
434
  default_input_type(:boolean).should == :boolean
340
435
  end
341
-
436
+
342
437
  it 'should default to :string for :string column types' do
343
438
  default_input_type(:string).should == :string
344
439
  end
345
-
440
+
346
441
  it 'should default to :number for :integer, :float and :decimal column types' do
347
442
  default_input_type(:integer).should == :number
348
443
  default_input_type(:float).should == :number
349
444
  default_input_type(:decimal).should == :number
350
445
  end
351
-
446
+
352
447
  it 'should default to :country for :string columns named country' do
353
448
  default_input_type(:string, :country).should == :country
354
449
  end
355
-
450
+
356
451
  it 'should default to :email for :string columns matching email' do
357
452
  default_input_type(:string, :email).should == :email
358
453
  default_input_type(:string, :customer_email).should == :email
359
454
  default_input_type(:string, :email_work).should == :email
360
455
  end
361
-
456
+
362
457
  it 'should default to :url for :string columns named url or website' do
363
458
  default_input_type(:string, :url).should == :url
364
459
  default_input_type(:string, :website).should == :url
365
460
  default_input_type(:string, :my_url).should == :url
366
461
  default_input_type(:string, :hurl).should_not == :url
367
462
  end
368
-
463
+
369
464
  it 'should default to :phone for :string columns named phone or fax' do
370
465
  default_input_type(:string, :phone).should == :phone
371
466
  default_input_type(:string, :fax).should == :phone
372
467
  end
373
-
468
+
374
469
  it 'should default to :search for :string columns named search' do
375
470
  default_input_type(:string, :search).should == :search
376
471
  end
377
-
472
+
378
473
  describe 'defaulting to file column' do
379
474
  Formtastic::FormBuilder.file_methods.each do |method|
380
475
  it "should default to :file for attributes that respond to ##{method}" do
381
- @new_post.stub!(:column_for_attribute).and_return(nil)
382
476
  column = mock('column')
383
-
477
+
384
478
  Formtastic::FormBuilder.file_methods.each do |test|
385
479
  ### TODO: Check if this is ok
386
480
  column.stub!(method).with(test).and_return(method == test)
387
481
  end
388
-
482
+
389
483
  @new_post.should_receive(method).and_return(column)
390
-
484
+
391
485
  semantic_form_for(@new_post) do |builder|
392
486
  builder.send(:default_input_type, method).should == :file
393
487
  end
394
488
  end
395
489
  end
396
-
490
+
397
491
  end
398
492
  end
399
-
493
+
400
494
  it 'should call the corresponding input class with .to_html' do
401
495
  [:select, :time_zone, :radio, :date, :datetime, :time, :boolean, :check_boxes, :hidden, :string, :password, :number, :text, :file].each do |input_style|
402
496
  @new_post.stub!(:generic_column_name)
@@ -405,7 +499,7 @@ describe 'Formtastic::FormBuilder#input' do
405
499
  input_instance = mock('Input instance')
406
500
  input_class = "#{input_style.to_s}_input".classify
407
501
  input_constant = "Formtastic::Inputs::#{input_class}".constantize
408
-
502
+
409
503
  input_constant.should_receive(:new).and_return(input_instance)
410
504
  input_instance.should_receive(:to_html).and_return("some HTML")
411
505
 
@@ -738,10 +832,10 @@ describe 'Formtastic::FormBuilder#input' do
738
832
  end
739
833
 
740
834
  describe ':collection option' do
741
-
835
+
742
836
  it "should be required on polymorphic associations" do
743
837
  @new_post.stub!(:commentable)
744
- @new_post.class.stub!(:reflections).and_return({
838
+ @new_post.class.stub!(:reflections).and_return({
745
839
  :commentable => mock('macro_reflection', :options => { :polymorphic => true }, :macro => :belongs_to)
746
840
  })
747
841
  @new_post.stub!(:column_for_attribute).with(:commentable).and_return(
@@ -750,37 +844,37 @@ describe 'Formtastic::FormBuilder#input' do
750
844
  @new_post.class.stub!(:reflect_on_association).with(:commentable).and_return(
751
845
  mock('reflection', :macro => :belongs_to, :options => { :polymorphic => true })
752
846
  )
753
- expect {
847
+ expect {
754
848
  concat(semantic_form_for(@new_post) do |builder|
755
- concat(builder.inputs do
849
+ concat(builder.inputs do
756
850
  concat(builder.input :commentable)
757
851
  end)
758
852
  end)
759
853
  }.to raise_error(Formtastic::PolymorphicInputWithoutCollectionError)
760
854
  end
761
-
855
+
762
856
  end
763
857
 
764
858
  end
765
-
859
+
766
860
  describe 'options re-use' do
767
-
861
+
768
862
  it 'should retain :as option when re-using the same options hash' do
769
863
  my_options = { :as => :string }
770
864
  output = ''
771
-
865
+
772
866
  concat(semantic_form_for(@new_post) do |builder|
773
867
  concat(builder.input(:title, my_options))
774
868
  concat(builder.input(:publish_at, my_options))
775
869
  end)
776
870
  output_buffer.should have_tag 'li.string', :count => 2
777
871
  end
778
-
779
-
872
+
873
+
780
874
  end
781
-
875
+
782
876
  describe 'instantiating an input class' do
783
-
877
+
784
878
  context 'when a class does not exist' do
785
879
  it "should raise an error" do
786
880
  lambda {
@@ -790,9 +884,9 @@ describe 'Formtastic::FormBuilder#input' do
790
884
  }.should raise_error(Formtastic::UnknownInputError)
791
885
  end
792
886
  end
793
-
887
+
794
888
  context 'when a customized top-level class does not exist' do
795
-
889
+
796
890
  it 'should instantiate the Formtastic input' do
797
891
  input = mock('input', :to_html => 'some HTML')
798
892
  Formtastic::Inputs::StringInput.should_receive(:new).and_return(input)
@@ -800,26 +894,26 @@ describe 'Formtastic::FormBuilder#input' do
800
894
  builder.input(:title, :as => :string)
801
895
  end)
802
896
  end
803
-
897
+
804
898
  end
805
-
899
+
806
900
  describe 'when a top-level input class exists' do
807
901
  it "should instantiate the top-level input instead of the Formtastic one" do
808
902
  class ::StringInput < Formtastic::Inputs::StringInput
809
903
  end
810
-
904
+
811
905
  input = mock('input', :to_html => 'some HTML')
812
906
  Formtastic::Inputs::StringInput.should_not_receive(:new).and_return(input)
813
907
  ::StringInput.should_receive(:new).and_return(input)
814
-
908
+
815
909
  concat(semantic_form_for(@new_post) do |builder|
816
910
  builder.input(:title, :as => :string)
817
911
  end)
818
912
  end
819
913
  end
820
-
914
+
821
915
  describe 'when instantiated multiple times with the same input type' do
822
-
916
+
823
917
  it "should be cached (not calling the internal methods)" do
824
918
  # TODO this is really tied to the underlying implementation
825
919
  concat(semantic_form_for(@new_post) do |builder|
@@ -828,10 +922,10 @@ describe 'Formtastic::FormBuilder#input' do
828
922
  builder.input(:title, :as => :string)
829
923
  end)
830
924
  end
831
-
925
+
832
926
  end
833
-
927
+
834
928
  end
835
-
929
+
836
930
  end
837
931