formtastic-bootstrap 1.0.0

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 (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
+