formtastic 2.0.0.rc3 → 2.0.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
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