formtastic-bootstrap 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +12 -0
  4. data/Gemfile.lock +123 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +159 -0
  7. data/Rakefile +49 -0
  8. data/VERSION +1 -0
  9. data/formtastic-bootstrap.gemspec +128 -0
  10. data/lib/action_view/helpers/text_field_date_helper.rb +166 -0
  11. data/lib/formtastic-bootstrap.rb +5 -0
  12. data/lib/formtastic-bootstrap/form_builder.rb +38 -0
  13. data/lib/formtastic-bootstrap/helpers.rb +19 -0
  14. data/lib/formtastic-bootstrap/helpers/buttons_helper.rb +47 -0
  15. data/lib/formtastic-bootstrap/helpers/fieldset_wrapper.rb +37 -0
  16. data/lib/formtastic-bootstrap/helpers/input_helper.rb +12 -0
  17. data/lib/formtastic-bootstrap/helpers/inputs_helper.rb +36 -0
  18. data/lib/formtastic-bootstrap/inputs.rb +28 -0
  19. data/lib/formtastic-bootstrap/inputs/base.rb +22 -0
  20. data/lib/formtastic-bootstrap/inputs/base/choices.rb +49 -0
  21. data/lib/formtastic-bootstrap/inputs/base/errors.rb +48 -0
  22. data/lib/formtastic-bootstrap/inputs/base/hints.rb +27 -0
  23. data/lib/formtastic-bootstrap/inputs/base/html.rb +21 -0
  24. data/lib/formtastic-bootstrap/inputs/base/labelling.rb +18 -0
  25. data/lib/formtastic-bootstrap/inputs/base/stringish.rb +18 -0
  26. data/lib/formtastic-bootstrap/inputs/base/timeish.rb +35 -0
  27. data/lib/formtastic-bootstrap/inputs/base/wrapping.rb +56 -0
  28. data/lib/formtastic-bootstrap/inputs/boolean_input.rb +33 -0
  29. data/lib/formtastic-bootstrap/inputs/check_boxes_input.rb +35 -0
  30. data/lib/formtastic-bootstrap/inputs/date_input.rb +16 -0
  31. data/lib/formtastic-bootstrap/inputs/datetime_input.rb +19 -0
  32. data/lib/formtastic-bootstrap/inputs/email_input.rb +15 -0
  33. data/lib/formtastic-bootstrap/inputs/file_input.rb +14 -0
  34. data/lib/formtastic-bootstrap/inputs/hidden_input.rb +12 -0
  35. data/lib/formtastic-bootstrap/inputs/number_input.rb +15 -0
  36. data/lib/formtastic-bootstrap/inputs/password_input.rb +15 -0
  37. data/lib/formtastic-bootstrap/inputs/phone_input.rb +15 -0
  38. data/lib/formtastic-bootstrap/inputs/radio_input.rb +32 -0
  39. data/lib/formtastic-bootstrap/inputs/range_input.rb +15 -0
  40. data/lib/formtastic-bootstrap/inputs/search_input.rb +15 -0
  41. data/lib/formtastic-bootstrap/inputs/select_input.rb +14 -0
  42. data/lib/formtastic-bootstrap/inputs/string_input.rb +15 -0
  43. data/lib/formtastic-bootstrap/inputs/text_input.rb +14 -0
  44. data/lib/formtastic-bootstrap/inputs/time_input.rb +16 -0
  45. data/lib/formtastic-bootstrap/inputs/url_input.rb +14 -0
  46. data/spec/builder/errors_spec.rb +214 -0
  47. data/spec/builder/semantic_fields_for_spec.rb +130 -0
  48. data/spec/helpers/input_helper_spec.rb +956 -0
  49. data/spec/helpers/inputs_helper_spec.rb +577 -0
  50. data/spec/inputs/boolean_input_spec.rb +193 -0
  51. data/spec/inputs/check_boxes_input_spec.rb +439 -0
  52. data/spec/inputs/date_input_spec.rb +147 -0
  53. data/spec/inputs/datetime_input_spec.rb +101 -0
  54. data/spec/inputs/email_input_spec.rb +59 -0
  55. data/spec/inputs/file_input_spec.rb +63 -0
  56. data/spec/inputs/hidden_input_spec.rb +122 -0
  57. data/spec/inputs/number_input_spec.rb +787 -0
  58. data/spec/inputs/password_input_spec.rb +73 -0
  59. data/spec/inputs/phone_input_spec.rb +59 -0
  60. data/spec/inputs/radio_input_spec.rb +240 -0
  61. data/spec/inputs/range_input_spec.rb +479 -0
  62. data/spec/inputs/search_input_spec.rb +59 -0
  63. data/spec/inputs/select_input_spec.rb +567 -0
  64. data/spec/inputs/string_input_spec.rb +182 -0
  65. data/spec/inputs/text_input_spec.rb +163 -0
  66. data/spec/inputs/time_input_spec.rb +206 -0
  67. data/spec/inputs/url_input_spec.rb +59 -0
  68. data/spec/spec_helper.rb +24 -0
  69. data/spec/support/custom_macros.rb +704 -0
  70. data/spec/support/depracation.rb +6 -0
  71. data/spec/support/formtastic_spec_helper.rb +382 -0
  72. metadata +204 -0
@@ -0,0 +1,956 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'FormtasticBootstrap::FormBuilder#input' do
5
+
6
+ include FormtasticSpecHelper
7
+
8
+ before do
9
+ @output_buffer = ''
10
+ mock_everything
11
+
12
+ Formtastic::Helpers::FormHelper.builder = FormtasticBootstrap::FormBuilder
13
+
14
+ @errors = mock('errors')
15
+ @errors.stub!(:[]).and_return([])
16
+ @new_post.stub!(:errors).and_return(@errors)
17
+ end
18
+
19
+ after do
20
+ ::I18n.backend.reload!
21
+ end
22
+
23
+ describe 'arguments and options' do
24
+
25
+ it 'should require the first argument (the method on form\'s object)' do
26
+ lambda {
27
+ concat(semantic_form_for(@new_post) do |builder|
28
+ concat(builder.input()) # no args passed in at all
29
+ end)
30
+ }.should raise_error(ArgumentError)
31
+ end
32
+
33
+ describe ':required option' do
34
+
35
+ describe 'when true' do
36
+
37
+ it 'should set a "required" class' do
38
+ with_config :required_string, " required yo!" do
39
+ concat(semantic_form_for(@new_post) do |builder|
40
+ concat(builder.input(:title, :required => true))
41
+ end)
42
+ output_buffer.should_not have_tag('form div.clearfix.optional')
43
+ output_buffer.should have_tag('form div.clearfix.required')
44
+ end
45
+ end
46
+
47
+ it 'should append the "required" string to the label' do
48
+ with_config :required_string, " required yo!" do
49
+ concat(semantic_form_for(@new_post) do |builder|
50
+ concat(builder.input(:title, :required => true))
51
+ end)
52
+ output_buffer.should have_tag('form div.clearfix.required label', /required yo/)
53
+ end
54
+ end
55
+ end
56
+
57
+ describe 'when false' do
58
+
59
+ before(:each) do
60
+ @orig_optional_string = FormtasticBootstrap::FormBuilder.optional_string
61
+ @string = FormtasticBootstrap::FormBuilder.optional_string = " optional yo!" # ensure there's something in the string
62
+ @new_post.class.should_not_receive(:reflect_on_all_validations)
63
+ end
64
+
65
+ after(:each) do
66
+ FormtasticBootstrap::FormBuilder.optional_string = @orig_optional_string
67
+ end
68
+
69
+ it 'should set an "optional" class' do
70
+ concat(semantic_form_for(@new_post) do |builder|
71
+ concat(builder.input(:title, :required => false))
72
+ end)
73
+ output_buffer.should_not have_tag('form div.clearfix.required')
74
+ output_buffer.should have_tag('form div.clearfix.optional')
75
+ end
76
+
77
+ it 'should set and "optional" class also when there is presence validator' do
78
+ @new_post.class.should_receive(:validators_on).with(:title).any_number_of_times.and_return([
79
+ active_model_presence_validator([:title])
80
+ ])
81
+ concat(semantic_form_for(@new_post) do |builder|
82
+ concat(builder.input(:title, :required => false))
83
+ end)
84
+ output_buffer.should_not have_tag('form div.clearfix.required')
85
+ output_buffer.should have_tag('form div.clearfix.optional')
86
+ end
87
+
88
+ it 'should append the "optional" string to the label' do
89
+ concat(semantic_form_for(@new_post) do |builder|
90
+ concat(builder.input(:title, :required => false))
91
+ end)
92
+ output_buffer.should have_tag('form div.clearfix.optional label', /#{@string}$/)
93
+ end
94
+
95
+ end
96
+
97
+ describe 'when not provided' do
98
+
99
+ describe 'and an object was not given' do
100
+
101
+ before(:each) do
102
+ @orig_all_fields_required = FormtasticBootstrap::FormBuilder.all_fields_required_by_default
103
+ end
104
+
105
+ after(:each) do
106
+ FormtasticBootstrap::FormBuilder.all_fields_required_by_default = @orig_all_fields_required
107
+ end
108
+
109
+ it 'should use the default value' do
110
+ FormtasticBootstrap::FormBuilder.all_fields_required_by_default.should == true
111
+ FormtasticBootstrap::FormBuilder.all_fields_required_by_default = false
112
+
113
+ concat(semantic_form_for(:project, :url => 'http://test.host/') do |builder|
114
+ concat(builder.input(:title))
115
+ end)
116
+ output_buffer.should_not have_tag('form div.clearfix.required')
117
+ output_buffer.should have_tag('form div.clearfix.optional')
118
+
119
+ end
120
+
121
+ end
122
+
123
+ describe 'and an object with :validators_on was given (ActiveModel, Active Resource)' do
124
+ before do
125
+ @new_post.stub!(:class).and_return(::PostModel)
126
+ end
127
+
128
+ after do
129
+ @new_post.stub!(:class).and_return(::Post)
130
+ end
131
+ describe 'and validates_presence_of was called for the method' do
132
+ it 'should be required' do
133
+
134
+ @new_post.class.should_receive(:validators_on).with(:title).any_number_of_times.and_return([
135
+ active_model_presence_validator([:title])
136
+ ])
137
+
138
+ @new_post.class.should_receive(:validators_on).with(:body).any_number_of_times.and_return([
139
+ active_model_presence_validator([:body], {:if => true})
140
+ ])
141
+
142
+ concat(semantic_form_for(@new_post) do |builder|
143
+ concat(builder.input(:title))
144
+ concat(builder.input(:body))
145
+ end)
146
+ output_buffer.should have_tag('form div.clearfix.required')
147
+ output_buffer.should_not have_tag('form div.clearfix.optional')
148
+ end
149
+
150
+ it 'should be required when there is :on => :create option on create' do
151
+ with_config :required_string, " required yo!" do
152
+ @new_post.class.should_receive(:validators_on).with(:title).any_number_of_times.and_return([
153
+ active_model_presence_validator([:title], {:on => :create})
154
+ ])
155
+ concat(semantic_form_for(@new_post) do |builder|
156
+ concat(builder.input(:title))
157
+ end)
158
+ output_buffer.should have_tag('form div.clearfix.required')
159
+ output_buffer.should_not have_tag('form div.clearfix.optional')
160
+ end
161
+ end
162
+
163
+ it 'should be required when there is :on => :save option on create' do
164
+ with_config :required_string, " required yo!" do
165
+ @new_post.class.should_receive(:validators_on).with(:title).any_number_of_times.and_return([
166
+ active_model_presence_validator([:title], {:on => :save})
167
+ ])
168
+ concat(semantic_form_for(@new_post) do |builder|
169
+ concat(builder.input(:title))
170
+ end)
171
+ output_buffer.should have_tag('form div.clearfix.required')
172
+ output_buffer.should_not have_tag('form div.clearfix.optional')
173
+ end
174
+ end
175
+
176
+ it 'should be required when there is :on => :save option on update' do
177
+ with_config :required_string, " required yo!" do
178
+ @fred.class.should_receive(:validators_on).with(:login).any_number_of_times.and_return([
179
+ active_model_presence_validator([:login], {:on => :save})
180
+ ])
181
+ concat(semantic_form_for(@fred) do |builder|
182
+ concat(builder.input(:login))
183
+ end)
184
+ output_buffer.should have_tag('form div.clearfix.required')
185
+ output_buffer.should_not have_tag('form div.clearfix.optional')
186
+ end
187
+ end
188
+
189
+ it 'should not be required when there is :on => :create option on update' do
190
+ @fred.class.should_receive(:validators_on).with(:login).any_number_of_times.and_return([
191
+ active_model_presence_validator([:login], {:on => :create})
192
+ ])
193
+ concat(semantic_form_for(@fred) do |builder|
194
+ concat(builder.input(:login))
195
+ end)
196
+ output_buffer.should_not have_tag('form div.clearfix.required')
197
+ output_buffer.should have_tag('form div.clearfix.optional')
198
+ end
199
+
200
+ it 'should not be required when there is :on => :update option on create' do
201
+ @new_post.class.should_receive(:validators_on).with(:title).any_number_of_times.and_return([
202
+ active_model_presence_validator([:title], {:on => :update})
203
+ ])
204
+ concat(semantic_form_for(@new_post) do |builder|
205
+ concat(builder.input(:title))
206
+ end)
207
+ output_buffer.should_not have_tag('form div.clearfix.required')
208
+ output_buffer.should have_tag('form div.clearfix.optional')
209
+ end
210
+
211
+ it 'should be not be required if the optional :if condition is not satisifed' do
212
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => false })
213
+ end
214
+
215
+ it 'should not be required if the optional :if proc evaluates to false' do
216
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => proc { |record| false } })
217
+ end
218
+
219
+ it 'should be required if the optional :if proc evaluates to true' do
220
+ presence_should_be_required(:required => true, :tag => :body, :options => { :if => proc { |record| true } })
221
+ end
222
+
223
+ it 'should not be required if the optional :unless proc evaluates to true' do
224
+ presence_should_be_required(:required => false, :tag => :body, :options => { :unless => proc { |record| true } })
225
+ end
226
+
227
+ it 'should be required if the optional :unless proc evaluates to false' do
228
+ presence_should_be_required(:required => true, :tag => :body, :options => { :unless => proc { |record| false } })
229
+ end
230
+
231
+ it 'should be required if the optional :if with a method string evaluates to true' do
232
+ @new_post.should_receive(:required_condition).and_return(true)
233
+ presence_should_be_required(:required => true, :tag => :body, :options => { :if => :required_condition })
234
+ end
235
+
236
+ it 'should be required if the optional :if with a method string evaluates to false' do
237
+ @new_post.should_receive(:required_condition).and_return(false)
238
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => :required_condition })
239
+ end
240
+
241
+ it 'should be required if the optional :unless with a method string evaluates to false' do
242
+ @new_post.should_receive(:required_condition).and_return(false)
243
+ presence_should_be_required(:required => true, :tag => :body, :options => { :unless => :required_condition })
244
+ end
245
+
246
+ it 'should not be required if the optional :unless with a method string evaluates to true' do
247
+ @new_post.should_receive(:required_condition).and_return(true)
248
+ presence_should_be_required(:required => false, :tag => :body, :options => { :unless => :required_condition })
249
+ end
250
+ end
251
+
252
+ describe 'and validates_inclusion_of was called for the method' do
253
+ it 'should be required' do
254
+ @new_post.class.should_receive(:validators_on).with(:published).any_number_of_times.and_return([
255
+ active_model_inclusion_validator([:published], {:in => [false, true]})
256
+ ])
257
+ should_be_required(:tag => :published, :required => true)
258
+ end
259
+
260
+ it 'should not be required if allow_blank is true' do
261
+ @new_post.class.should_receive(:validators_on).with(:published).any_number_of_times.and_return([
262
+ active_model_inclusion_validator([:published], {:in => [false, true], :allow_blank => true})
263
+ ])
264
+ should_be_required(:tag => :published, :required => false)
265
+ end
266
+ end
267
+
268
+ describe 'and validates_length_of was called for the method' do
269
+ it 'should be required if minimum is set' do
270
+ length_should_be_required(:tag => :title, :required => true, :options => {:minimum => 1})
271
+ end
272
+
273
+ it 'should be required if :within is set' do
274
+ length_should_be_required(:tag => :title, :required => true, :options => {:within => 1..5})
275
+ end
276
+
277
+ it 'should not be required if :within allows zero length' do
278
+ length_should_be_required(:tag => :title, :required => false, :options => {:within => 0..5})
279
+ end
280
+
281
+ it 'should not be required if only :minimum is zero' do
282
+ length_should_be_required(:tag => :title, :required => false, :options => {:minimum => 0})
283
+ end
284
+
285
+ it 'should not be required if only :minimum is not set' do
286
+ length_should_be_required(:tag => :title, :required => false, :options => {:maximum => 5})
287
+ end
288
+
289
+ it 'should not be required if allow_blank is true' do
290
+ length_should_be_required(:tag => :published, :required => false, :options => {:allow_blank => true})
291
+ end
292
+ end
293
+
294
+ def add_presence_validator(options)
295
+ @new_post.class.stub!(:validators_on).with(options[:tag]).and_return([
296
+ active_model_presence_validator([options[:tag]], options[:options])
297
+ ])
298
+ end
299
+
300
+ def add_length_validator(options)
301
+ @new_post.class.should_receive(:validators_on).with(options[:tag]).any_number_of_times {[
302
+ active_model_length_validator([options[:tag]], options[:options])
303
+ ]}
304
+ end
305
+
306
+ # TODO make a matcher for this?
307
+ def should_be_required(options)
308
+ concat(semantic_form_for(@new_post) do |builder|
309
+ concat(builder.input(options[:tag]))
310
+ end)
311
+
312
+ if options[:required]
313
+ output_buffer.should_not have_tag('form div.clearfix.optional')
314
+ output_buffer.should have_tag('form div.clearfix.required')
315
+ else
316
+ output_buffer.should have_tag('form div.clearfix.optional')
317
+ output_buffer.should_not have_tag('form div.clearfix.required')
318
+ end
319
+ end
320
+
321
+ def presence_should_be_required(options)
322
+ add_presence_validator(options)
323
+ should_be_required(options)
324
+ end
325
+
326
+ def length_should_be_required(options)
327
+ add_length_validator(options)
328
+ should_be_required(options)
329
+ end
330
+
331
+ # TODO JF reversed this during refactor, need to make sure
332
+ describe 'and there are no requirement validations on the method' do
333
+ before do
334
+ @new_post.class.should_receive(:validators_on).with(:title).and_return([])
335
+ end
336
+
337
+ it 'should not be required' do
338
+ concat(semantic_form_for(@new_post) do |builder|
339
+ concat(builder.input(:title))
340
+ end)
341
+ output_buffer.should_not have_tag('form div.clearfix.required')
342
+ output_buffer.should have_tag('form div.clearfix.optional')
343
+ end
344
+ end
345
+
346
+ end
347
+
348
+ describe 'and an object without :validators_on' do
349
+
350
+ before(:each) do
351
+ @orig_all_fields_required = FormtasticBootstrap::FormBuilder.all_fields_required_by_default
352
+ end
353
+
354
+ after(:each) do
355
+ FormtasticBootstrap::FormBuilder.all_fields_required_by_default = @orig_all_fields_required
356
+ end
357
+
358
+ it 'should use the default value' do
359
+ FormtasticBootstrap::FormBuilder.all_fields_required_by_default.should == true
360
+ FormtasticBootstrap::FormBuilder.all_fields_required_by_default = false
361
+
362
+ concat(semantic_form_for(@new_post) do |builder|
363
+ concat(builder.input(:title))
364
+ end)
365
+ output_buffer.should_not have_tag('form div.clearfix.required')
366
+ output_buffer.should have_tag('form div.clearfix.optional')
367
+
368
+ # Formtastic::FormBuilder.all_fields_required_by_default = true
369
+ end
370
+
371
+ end
372
+
373
+ end
374
+
375
+ end
376
+
377
+ describe ':as option' do
378
+
379
+ describe 'when not provided' do
380
+
381
+ it 'should default to a string for forms without objects unless column is password' do
382
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
383
+ concat(builder.input(:anything))
384
+ end)
385
+ output_buffer.should have_tag('form div.clearfix.string')
386
+ end
387
+
388
+ it 'should default to password for forms without objects if column is password' do
389
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
390
+ concat(builder.input(:password))
391
+ concat(builder.input(:password_confirmation))
392
+ concat(builder.input(:confirm_password))
393
+ end)
394
+ output_buffer.should have_tag('form div.clearfix.password', :count => 3)
395
+ end
396
+
397
+ it 'should default to a string for methods on objects that don\'t respond to "column_for_attribute"' do
398
+ @new_post.stub!(:method_without_a_database_column)
399
+ @new_post.stub!(:column_for_attribute).and_return(nil)
400
+ default_input_type(nil, :method_without_a_database_column).should == :string
401
+ end
402
+
403
+ it 'should default to :password for methods that don\'t have a column in the database but "password" is in the method name' do
404
+ @new_post.stub!(:password_method_without_a_database_column)
405
+ @new_post.stub!(:column_for_attribute).and_return(nil)
406
+ default_input_type(nil, :password_method_without_a_database_column).should == :password
407
+ end
408
+
409
+ it 'should default to :password for methods on objects that don\'t respond to "column_for_attribute" but "password" is in the method name' do
410
+ @new_post.stub!(:password_method_without_a_database_column)
411
+ @new_post.stub!(:column_for_attribute).and_return(nil)
412
+ default_input_type(nil, :password_method_without_a_database_column).should == :password
413
+ end
414
+
415
+ it 'should default to :number for "integer" column with name ending in "_id"' do
416
+ @new_post.stub!(:aws_instance_id)
417
+ @new_post.stub!(:column_for_attribute).with(:aws_instance_id).and_return(mock('column', :type => :integer))
418
+ default_input_type(:integer, :aws_instance_id).should == :number
419
+ end
420
+
421
+ it 'should default to :select for associations' do
422
+ @new_post.class.stub!(:reflect_on_association).with(:user_id).and_return(mock('ActiveRecord::Reflection::AssociationReflection'))
423
+ @new_post.class.stub!(:reflect_on_association).with(:section_id).and_return(mock('ActiveRecord::Reflection::AssociationReflection'))
424
+ default_input_type(:integer, :user_id).should == :select
425
+ default_input_type(:integer, :section_id).should == :select
426
+ end
427
+
428
+ it 'should default to :password for :string column types with "password" in the method name' do
429
+ default_input_type(:string, :password).should == :password
430
+ default_input_type(:string, :hashed_password).should == :password
431
+ default_input_type(:string, :password_hash).should == :password
432
+ end
433
+
434
+ it 'should default to :text for :text column types' do
435
+ default_input_type(:text).should == :text
436
+ end
437
+
438
+ it 'should default to :date for :date column types' do
439
+ default_input_type(:date).should == :date
440
+ end
441
+
442
+ it 'should default to :datetime for :datetime and :timestamp column types' do
443
+ default_input_type(:datetime).should == :datetime
444
+ default_input_type(:timestamp).should == :datetime
445
+ end
446
+
447
+ it 'should default to :time for :time column types' do
448
+ default_input_type(:time).should == :time
449
+ end
450
+
451
+ it 'should default to :boolean for :boolean column types' do
452
+ default_input_type(:boolean).should == :boolean
453
+ end
454
+
455
+ it 'should default to :string for :string column types' do
456
+ default_input_type(:string).should == :string
457
+ end
458
+
459
+ it 'should default to :number for :integer, :float and :decimal column types' do
460
+ default_input_type(:integer).should == :number
461
+ default_input_type(:float).should == :number
462
+ default_input_type(:decimal).should == :number
463
+ end
464
+
465
+ it 'should default to :country for :string columns named country' do
466
+ default_input_type(:string, :country).should == :country
467
+ end
468
+
469
+ it 'should default to :email for :string columns matching email' do
470
+ default_input_type(:string, :email).should == :email
471
+ default_input_type(:string, :customer_email).should == :email
472
+ default_input_type(:string, :email_work).should == :email
473
+ end
474
+
475
+ it 'should default to :url for :string columns named url or website' do
476
+ default_input_type(:string, :url).should == :url
477
+ default_input_type(:string, :website).should == :url
478
+ default_input_type(:string, :my_url).should == :url
479
+ default_input_type(:string, :hurl).should_not == :url
480
+ end
481
+
482
+ it 'should default to :phone for :string columns named phone or fax' do
483
+ default_input_type(:string, :phone).should == :phone
484
+ default_input_type(:string, :fax).should == :phone
485
+ end
486
+
487
+ it 'should default to :search for :string columns named search' do
488
+ default_input_type(:string, :search).should == :search
489
+ end
490
+
491
+ describe 'defaulting to file column' do
492
+ FormtasticBootstrap::FormBuilder.file_methods.each do |method|
493
+ it "should default to :file for attributes that respond to ##{method}" do
494
+ column = mock('column')
495
+
496
+ FormtasticBootstrap::FormBuilder.file_methods.each do |test|
497
+ ### TODO: Check if this is ok
498
+ column.stub!(method).with(test).and_return(method == test)
499
+ end
500
+
501
+ @new_post.should_receive(method).and_return(column)
502
+
503
+ semantic_form_for(@new_post) do |builder|
504
+ builder.send(:default_input_type, method).should == :file
505
+ end
506
+ end
507
+ end
508
+
509
+ end
510
+ end
511
+
512
+ it 'should call the corresponding input class with .to_html' do
513
+ # TODO Re-activate timezone test.
514
+ # TODO Add for fancy Bootstrap types.
515
+ # [:select, :time_zone, :radio, :date, :datetime, :time, :boolean, :check_boxes, :hidden, :string, :password, :number, :text, :file].each do |input_style|
516
+ [:select, :radio, :date, :datetime, :time, :boolean, :check_boxes, :hidden, :string, :password, :number, :text, :file].each do |input_style|
517
+ @new_post.stub!(:generic_column_name)
518
+ @new_post.stub!(:column_for_attribute).and_return(mock('column', :type => :string, :limit => 255))
519
+ semantic_form_for(@new_post) do |builder|
520
+ input_instance = mock('Input instance')
521
+ input_class = "#{input_style.to_s}_input".classify
522
+ input_constant = "FormtasticBootstrap::Inputs::#{input_class}".constantize
523
+
524
+ input_constant.should_receive(:new).and_return(input_instance)
525
+ input_instance.should_receive(:to_html).and_return("some HTML")
526
+
527
+ concat(builder.input(:generic_column_name, :as => input_style))
528
+ end
529
+ end
530
+ end
531
+
532
+ end
533
+
534
+ describe ':label option' do
535
+
536
+ describe 'when provided' do
537
+ it 'should be passed down to the label tag' do
538
+ concat(semantic_form_for(@new_post) do |builder|
539
+ concat(builder.input(:title, :label => "Kustom"))
540
+ end)
541
+ output_buffer.should have_tag("form div.clearfix label", /Kustom/)
542
+ end
543
+
544
+ it 'should not generate a label if false' do
545
+ concat(semantic_form_for(@new_post) do |builder|
546
+ concat(builder.input(:title, :label => false))
547
+ end)
548
+ output_buffer.should_not have_tag("form div.clearfix label")
549
+ end
550
+
551
+ it 'should be dupped if frozen' do
552
+ concat(semantic_form_for(@new_post) do |builder|
553
+ concat(builder.input(:title, :label => "Kustom".freeze))
554
+ end)
555
+ output_buffer.should have_tag("form div.clearfix label", /Kustom/)
556
+ end
557
+ end
558
+
559
+ describe 'when not provided' do
560
+ describe 'when localized label is provided' do
561
+ describe 'and object is given' do
562
+ describe 'and label_str_method not :humanize' do
563
+ it 'should render a label with localized text and not apply the label_str_method' do
564
+ with_config :label_str_method, :reverse do
565
+ @localized_label_text = 'Localized title'
566
+ @new_post.stub!(:meta_description)
567
+ ::I18n.backend.store_translations :en,
568
+ :formtastic => {
569
+ :labels => {
570
+ :meta_description => @localized_label_text
571
+ }
572
+ }
573
+
574
+ concat(semantic_form_for(@new_post) do |builder|
575
+ concat(builder.input(:meta_description))
576
+ end)
577
+ output_buffer.should have_tag('form div.clearfix label', /Localized title/)
578
+ end
579
+ end
580
+ end
581
+ end
582
+ end
583
+
584
+ describe 'when localized label is NOT provided' do
585
+ describe 'and object is not given' do
586
+ it 'should default the humanized method name, passing it down to the label tag' do
587
+ ::I18n.backend.store_translations :en, :formtastic => {}
588
+ with_config :label_str_method, :humanize do
589
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
590
+ concat(builder.input(:meta_description))
591
+ end)
592
+ output_buffer.should have_tag("form div.clearfix label", /#{'meta_description'.humanize}/)
593
+ end
594
+ end
595
+ end
596
+
597
+ describe 'and object is given' do
598
+ it 'should delegate the label logic to class human attribute name and pass it down to the label tag' do
599
+ @new_post.stub!(:meta_description) # a two word method name
600
+ @new_post.class.should_receive(:human_attribute_name).with('meta_description').and_return('meta_description'.humanize)
601
+
602
+ concat(semantic_form_for(@new_post) do |builder|
603
+ concat(builder.input(:meta_description))
604
+ end)
605
+ output_buffer.should have_tag("form div.clearfix label", /#{'meta_description'.humanize}/)
606
+ end
607
+ end
608
+
609
+ describe 'and object is given with label_str_method set to :capitalize' do
610
+ it 'should capitalize method name, passing it down to the label tag' do
611
+ with_config :label_str_method, :capitalize do
612
+ @new_post.stub!(:meta_description)
613
+
614
+ concat(semantic_form_for(@new_post) do |builder|
615
+ concat(builder.input(:meta_description))
616
+ end)
617
+ output_buffer.should have_tag("form div.clearfix label", /#{'meta_description'.capitalize}/)
618
+ end
619
+ end
620
+ end
621
+ end
622
+
623
+ describe 'when localized label is provided' do
624
+ before do
625
+ @localized_label_text = 'Localized title'
626
+ @default_localized_label_text = 'Default localized title'
627
+ ::I18n.backend.store_translations :en,
628
+ :formtastic => {
629
+ :labels => {
630
+ :title => @default_localized_label_text,
631
+ :published => @default_localized_label_text,
632
+ :post => {
633
+ :title => @localized_label_text,
634
+ :published => @default_localized_label_text
635
+ }
636
+ }
637
+ }
638
+ end
639
+
640
+ it 'should render a label with localized label (I18n)' do
641
+ with_config :i18n_lookups_by_default, false do
642
+ concat(semantic_form_for(@new_post) do |builder|
643
+ concat(builder.input(:title, :label => true))
644
+ concat(builder.input(:published, :as => :boolean, :label => true))
645
+ end)
646
+ output_buffer.should have_tag('form div.clearfix label', Regexp.new('^' + @localized_label_text))
647
+ end
648
+ end
649
+
650
+ it 'should render a hint paragraph containing an optional localized label (I18n) if first is not set' do
651
+ with_config :i18n_lookups_by_default, false do
652
+ ::I18n.backend.store_translations :en,
653
+ :formtastic => {
654
+ :labels => {
655
+ :post => {
656
+ :title => nil,
657
+ :published => nil
658
+ }
659
+ }
660
+ }
661
+ concat(semantic_form_for(@new_post) do |builder|
662
+ concat(builder.input(:title, :label => true))
663
+ concat(builder.input(:published, :as => :boolean, :label => true))
664
+ end)
665
+ output_buffer.should have_tag('form div.clearfix label', Regexp.new('^' + @default_localized_label_text))
666
+ end
667
+ end
668
+ end
669
+ end
670
+
671
+ end
672
+
673
+ describe ':hint option' do
674
+
675
+ describe 'when provided' do
676
+
677
+ before(:each) do
678
+ @orig_hint_class = FormtasticBootstrap::FormBuilder.default_inline_hint_class
679
+ end
680
+
681
+ after(:each) do
682
+ FormtasticBootstrap::FormBuilder.default_inline_hint_class = @orig_hint_class
683
+ end
684
+
685
+ it 'should be passed down to the paragraph tag' do
686
+ hint_text = "this is the title of the post"
687
+ concat(semantic_form_for(@new_post) do |builder|
688
+ concat(builder.input(:title, :hint => hint_text))
689
+ end)
690
+ output_buffer.should have_tag("form div.clearfix div span.help-inline", hint_text)
691
+ end
692
+
693
+ it 'should have a custom hint class if I ask for one' do
694
+ hint_text = "this is the title of the post"
695
+ concat(semantic_form_for(@new_post) do |builder|
696
+ concat(builder.input(:title, :hint => hint_text, :hint_class => 'custom-hint-class'))
697
+ end)
698
+ output_buffer.should have_tag("form div.clearfix div span.custom-hint-class", hint_text)
699
+ end
700
+
701
+ it 'should have a custom hint class defaulted for all forms' do
702
+ hint_text = "this is the title of the post"
703
+ # FormtasticBootstrap::FormBuilder.default_hint_class = "custom-hint-class"
704
+ FormtasticBootstrap::FormBuilder.default_inline_hint_class = "custom-hint-class"
705
+ concat(semantic_form_for(@new_post) do |builder|
706
+ concat(builder.input(:title, :hint => hint_text))
707
+ end)
708
+ output_buffer.should have_tag("form div.clearfix div span.custom-hint-class", hint_text)
709
+ end
710
+ end
711
+
712
+ describe 'when not provided' do
713
+ describe 'when localized hint (I18n) is provided' do
714
+ before(:each) do
715
+ @localized_hint_text = "This is the localized hint."
716
+ @default_localized_hint_text = "This is the default localized hint."
717
+ ::I18n.backend.store_translations :en,
718
+ :formtastic => {
719
+ :hints => {
720
+ :title => @default_localized_hint_text,
721
+ }
722
+ }
723
+ end
724
+
725
+ after(:each) do
726
+ ::I18n.backend.reload!
727
+ end
728
+
729
+ describe 'when provided value (hint value) is set to TRUE' do
730
+ it 'should render a hint paragraph containing a localized hint (I18n)' do
731
+ with_config :i18n_lookups_by_default, false do
732
+ ::I18n.backend.store_translations :en,
733
+ :formtastic => {
734
+ :hints => {
735
+ :post => {
736
+ :title => @localized_hint_text
737
+ }
738
+ }
739
+ }
740
+ concat(semantic_form_for(@new_post) do |builder|
741
+ concat(builder.input(:title, :hint => true))
742
+ end)
743
+ output_buffer.should have_tag('form div.clearfix div span.help-inline', @localized_hint_text)
744
+ end
745
+ end
746
+
747
+ it 'should render a hint paragraph containing a localized hint (I18n) with a custom hint class if i ask for one' do
748
+ with_config :i18n_lookups_by_default, false do
749
+ ::I18n.backend.store_translations :en,
750
+ :formtastic => {
751
+ :hints => {
752
+ :post => {
753
+ :title => @localized_hint_text
754
+ }
755
+ }
756
+ }
757
+ concat(semantic_form_for(@new_post) do |builder|
758
+ concat(builder.input(:title, :hint => true, :hint_class => 'custom-hint-class'))
759
+ end)
760
+ output_buffer.should have_tag('form div.clearfix div span.custom-hint-class', @localized_hint_text)
761
+ end
762
+ end
763
+
764
+ it 'should render a hint paragraph containing an optional localized hint (I18n) if first is not set' do
765
+ with_config :i18n_lookups_by_default, false do
766
+ concat(semantic_form_for(@new_post) do |builder|
767
+ concat(builder.input(:title, :hint => true))
768
+ end)
769
+ output_buffer.should have_tag('form div.clearfix div span.help-inline', @default_localized_hint_text)
770
+ end
771
+ end
772
+ end
773
+
774
+ describe 'when provided value (label value) is set to FALSE' do
775
+ it 'should not render a hint paragraph' do
776
+ with_config :i18n_lookups_by_default, false do
777
+ concat(semantic_form_for(@new_post) do |builder|
778
+ concat(builder.input(:title, :hint => false))
779
+ end)
780
+ output_buffer.should_not have_tag('form div.clearfix div span.help-inline', @localized_hint_text)
781
+ end
782
+ end
783
+ end
784
+ end
785
+
786
+ describe 'when localized hint (I18n) is a model with attribute hints' do
787
+ it "should see the provided hash as a blank entry" do
788
+ with_config :i18n_lookups_by_default, false do
789
+ ::I18n.backend.store_translations :en,
790
+ :formtastic => {
791
+ :hints => {
792
+ :title => { # movie title
793
+ :summary => @localized_hint_text # summary of movie
794
+ }
795
+ }
796
+ }
797
+ semantic_form_for(@new_post) do |builder|
798
+ concat(builder.input(:title, :hint => true))
799
+ end
800
+ output_buffer.should_not have_tag('form div.clearfix div span.help-inline', @localized_hint_text)
801
+ end
802
+ end
803
+ end
804
+
805
+ describe 'when localized hint (I18n) is not provided' do
806
+ it 'should not render a hint paragraph' do
807
+ with_config :i18n_lookups_by_default, false do
808
+ concat(semantic_form_for(@new_post) do |builder|
809
+ concat(builder.input(:title))
810
+ end)
811
+ output_buffer.should_not have_tag('form div.clearfix div span.help-inline')
812
+ end
813
+ end
814
+ end
815
+ end
816
+
817
+ end
818
+
819
+ describe ':wrapper_html option' do
820
+
821
+ describe 'when provided' do
822
+ it 'should be passed down to the li tag' do
823
+ concat(semantic_form_for(@new_post) do |builder|
824
+ concat(builder.input(:title, :wrapper_html => {:id => :another_id}))
825
+ end)
826
+ output_buffer.should have_tag("form div#another_id")
827
+ end
828
+
829
+ it 'should append given classes to li default classes' do
830
+ concat(semantic_form_for(@new_post) do |builder|
831
+ concat(builder.input(:title, :wrapper_html => {:class => :another_class}, :required => true))
832
+ end)
833
+ output_buffer.should have_tag("form div.clearfix.string")
834
+ output_buffer.should have_tag("form div.clearfix.required")
835
+ output_buffer.should have_tag("form div.clearfix.another_class")
836
+ end
837
+
838
+ it 'should allow classes to be an array' do
839
+ concat(semantic_form_for(@new_post) do |builder|
840
+ concat(builder.input(:title, :wrapper_html => {:class => [ :my_class, :another_class ]}))
841
+ end)
842
+ output_buffer.should have_tag("form div.clearfix.string")
843
+ output_buffer.should have_tag("form div.clearfix.my_class")
844
+ output_buffer.should have_tag("form div.clearfix.another_class")
845
+ end
846
+ end
847
+
848
+ # describe 'when not provided' do
849
+ # it 'should use default id and class' do
850
+ # concat(semantic_form_for(@new_post) do |builder|
851
+ # concat(builder.input(:title))
852
+ # end)
853
+ # output_buffer.should have_tag("form div#post_title_input")
854
+ # output_buffer.should have_tag("form div.clearfix.string")
855
+ # end
856
+ # end
857
+ #
858
+ end
859
+
860
+ describe ':collection option' do
861
+
862
+ it "should be required on polymorphic associations" do
863
+ @new_post.stub!(:commentable)
864
+ @new_post.class.stub!(:reflections).and_return({
865
+ :commentable => mock('macro_reflection', :options => { :polymorphic => true }, :macro => :belongs_to)
866
+ })
867
+ @new_post.stub!(:column_for_attribute).with(:commentable).and_return(
868
+ mock('column', :type => :integer)
869
+ )
870
+ @new_post.class.stub!(:reflect_on_association).with(:commentable).and_return(
871
+ mock('reflection', :macro => :belongs_to, :options => { :polymorphic => true })
872
+ )
873
+ expect {
874
+ concat(semantic_form_for(@new_post) do |builder|
875
+ concat(builder.inputs do
876
+ concat(builder.input :commentable)
877
+ end)
878
+ end)
879
+ }.to raise_error(Formtastic::PolymorphicInputWithoutCollectionError)
880
+ end
881
+
882
+ end
883
+
884
+ end
885
+
886
+ describe 'options re-use' do
887
+
888
+ it 'should retain :as option when re-using the same options hash' do
889
+ my_options = { :as => :string }
890
+ output = ''
891
+
892
+ concat(semantic_form_for(@new_post) do |builder|
893
+ concat(builder.input(:title, my_options))
894
+ concat(builder.input(:publish_at, my_options))
895
+ end)
896
+ output_buffer.should have_tag 'div.clearfix.string', :count => 2
897
+ end
898
+
899
+ end
900
+
901
+ describe 'instantiating an input class' do
902
+
903
+ context 'when a class does not exist' do
904
+ it "should raise an error" do
905
+ lambda {
906
+ concat(semantic_form_for(@new_post) do |builder|
907
+ builder.input(:title, :as => :non_existant)
908
+ end)
909
+ }.should raise_error(Formtastic::UnknownInputError)
910
+ end
911
+ end
912
+
913
+ context 'when a customized top-level class does not exist' do
914
+
915
+ it 'should instantiate the Formtastic input' do
916
+ input = mock('input', :to_html => 'some HTML')
917
+ FormtasticBootstrap::Inputs::StringInput.should_receive(:new).and_return(input)
918
+ concat(semantic_form_for(@new_post) do |builder|
919
+ builder.input(:title, :as => :string)
920
+ end)
921
+ end
922
+
923
+ end
924
+
925
+ describe 'when a top-level input class exists' do
926
+ it "should instantiate the top-level input instead of the Formtastic one" do
927
+ class ::StringInput < FormtasticBootstrap::Inputs::StringInput
928
+ end
929
+
930
+ input = mock('input', :to_html => 'some HTML')
931
+ FormtasticBootstrap::Inputs::StringInput.should_not_receive(:new).and_return(input)
932
+ ::StringInput.should_receive(:new).and_return(input)
933
+
934
+ concat(semantic_form_for(@new_post) do |builder|
935
+ builder.input(:title, :as => :string)
936
+ end)
937
+ end
938
+ end
939
+
940
+ describe 'when instantiated multiple times with the same input type' do
941
+
942
+ it "should be cached (not calling the internal methods)" do
943
+ # TODO this is really tied to the underlying implementation
944
+ concat(semantic_form_for(@new_post) do |builder|
945
+ builder.should_receive(:custom_input_class_name).with(:string).once.and_return(::Formtastic::Inputs::StringInput)
946
+ builder.input(:title, :as => :string)
947
+ builder.input(:title, :as => :string)
948
+ end)
949
+ end
950
+
951
+ end
952
+
953
+ end
954
+
955
+ end
956
+