formtastic 2.0.0.rc3 → 2.0.0.rc4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. data/.travis.yml +7 -0
  2. data/CHANGELOG +26 -0
  3. data/Gemfile +2 -0
  4. data/README.textile +0 -1
  5. data/RELEASE_PROCESS +0 -1
  6. data/Rakefile +1 -1
  7. data/app/assets/stylesheets/formtastic.css +3 -3
  8. data/app/assets/stylesheets/formtastic_ie6.css +7 -1
  9. data/app/assets/stylesheets/formtastic_ie7.css +7 -1
  10. data/formtastic.gemspec +0 -15
  11. data/lib/formtastic/form_builder.rb +2 -0
  12. data/lib/formtastic/helpers/errors_helper.rb +22 -0
  13. data/lib/formtastic/helpers/form_helper.rb +18 -16
  14. data/lib/formtastic/helpers/input_helper.rb +9 -7
  15. data/lib/formtastic/helpers/inputs_helper.rb +11 -3
  16. data/lib/formtastic/helpers/reflection.rb +5 -1
  17. data/lib/formtastic/inputs/base/collections.rb +7 -0
  18. data/lib/formtastic/inputs/base/html.rb +1 -1
  19. data/lib/formtastic/inputs/base/timeish.rb +7 -2
  20. data/lib/formtastic/inputs/base/validations.rb +39 -8
  21. data/lib/formtastic/inputs/check_boxes_input.rb +3 -3
  22. data/lib/formtastic/inputs/file_input.rb +4 -4
  23. data/lib/formtastic/inputs/number_input.rb +1 -1
  24. data/lib/formtastic/inputs/radio_input.rb +1 -1
  25. data/lib/formtastic/inputs/time_input.rb +25 -3
  26. data/lib/formtastic/version.rb +1 -1
  27. data/lib/generators/templates/formtastic.rb +10 -1
  28. data/spec/builder/errors_spec.rb +10 -0
  29. data/spec/builder/semantic_fields_for_spec.rb +77 -36
  30. data/spec/helpers/form_helper_spec.rb +32 -0
  31. data/spec/helpers/input_helper_spec.rb +196 -102
  32. data/spec/helpers/inputs_helper_spec.rb +85 -73
  33. data/spec/helpers/reflection_helper_spec.rb +32 -0
  34. data/spec/inputs/check_boxes_input_spec.rb +21 -6
  35. data/spec/inputs/date_input_spec.rb +20 -0
  36. data/spec/inputs/datetime_input_spec.rb +30 -11
  37. data/spec/inputs/label_spec.rb +8 -0
  38. data/spec/inputs/number_input_spec.rb +298 -2
  39. data/spec/inputs/radio_input_spec.rb +5 -6
  40. data/spec/inputs/string_input_spec.rb +22 -5
  41. data/spec/inputs/time_input_spec.rb +51 -7
  42. data/spec/spec_helper.rb +64 -12
  43. metadata +11 -9
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'Formtastic::Helpers::Reflection' do
5
+
6
+ include FormtasticSpecHelper
7
+
8
+ before do
9
+ @output_buffer = ''
10
+ mock_everything
11
+ end
12
+
13
+ class ReflectionTester
14
+ include Formtastic::Helpers::Reflection
15
+ def initialize(model_object)
16
+ @object = model_object
17
+ end
18
+ end
19
+
20
+ context 'with an ActiveRecord object' do
21
+ it "should return association details on an ActiveRecord association" do
22
+ @reflection_tester = ReflectionTester.new(@new_post)
23
+ @reflection_tester.reflection_for(:sub_posts).should_not be_nil
24
+ end
25
+ it "should return association details on a MongoMapper association" do
26
+ @reflection_tester = ReflectionTester.new(@new_mm_post)
27
+ @reflection_tester.reflection_for(:sub_posts).should_not be_nil
28
+ end
29
+ end
30
+
31
+
32
+ end
@@ -61,6 +61,10 @@ describe 'check_boxes input' do
61
61
  output_buffer.should_not have_tag('li.choice label.label')
62
62
  end
63
63
 
64
+ it 'should not be marked as required' do
65
+ output_buffer.should_not have_tag('li.choice input[@required]')
66
+ end
67
+
64
68
  it 'should contain a label for the radio input with a nested input and label text' do
65
69
  ::Post.all.each do |post|
66
70
  output_buffer.should have_tag('form li fieldset ol li label', /#{post.to_label}/)
@@ -74,12 +78,6 @@ describe 'check_boxes input' do
74
78
  end
75
79
  end
76
80
 
77
- it "should add the required attribute to the input's html options" do
78
- ::Post.all.each do |post|
79
- output_buffer.should have_tag("form li fieldset ol li.post_#{post.id} input[@required]")
80
- end
81
- end
82
-
83
81
  it 'should have a checkbox input but no hidden field for each post' do
84
82
  ::Post.all.each do |post|
85
83
  output_buffer.should have_tag("form li fieldset ol li label input#author_post_ids_#{post.id}")
@@ -358,6 +356,23 @@ describe 'check_boxes input' do
358
356
  end
359
357
  end
360
358
  end
359
+
360
+ describe 'when :collection is provided as an array of arrays' do
361
+ before do
362
+ @output_buffer = ''
363
+ mock_everything
364
+ @fred.stub(:genres) { ['fiction', 'biography'] }
365
+
366
+ concat(semantic_form_for(@fred) do |builder|
367
+ concat(builder.input(:genres, :as => :check_boxes, :collection => [['Fiction', 'fiction'], ['Non-fiction', 'non_fiction'], ['Biography', 'biography']]))
368
+ end)
369
+ end
370
+
371
+ it 'should check the correct checkboxes' do
372
+ output_buffer.should have_tag("form li fieldset ol li label input[@value='fiction'][@checked='checked']")
373
+ output_buffer.should have_tag("form li fieldset ol li label input[@value='biography'][@checked='checked']")
374
+ end
375
+ end
361
376
 
362
377
  describe "when namespace is provided" do
363
378
 
@@ -95,6 +95,26 @@ describe 'date input' do
95
95
  output_buffer.should have_tag('form li.date fieldset ol li label', /#{f}/i) unless field == f
96
96
  end
97
97
  end
98
+
99
+ it "should not display the label for the #{field} field when :labels[:#{field}] is false" do
100
+ output_buffer.replace ''
101
+ concat(semantic_form_for(@new_post) do |builder|
102
+ concat(builder.input(:created_at, :as => :date, :labels => { field => false }))
103
+ end)
104
+ output_buffer.should have_tag('form li.date fieldset ol li label', :count => fields.length-1)
105
+ fields.each do |f|
106
+ output_buffer.should have_tag('form li.date fieldset ol li label', /#{f}/i) unless field == f
107
+ end
108
+ end
109
+
110
+ it "should not render unsafe HTML when :labels[:#{field}] is false" do
111
+ output_buffer.replace ''
112
+ concat(semantic_form_for(@new_post) do |builder|
113
+ concat(builder.input(:created_at, :as => :time, :include_seconds => true, :labels => { field => false }))
114
+ end)
115
+ output_buffer.should_not include(">")
116
+ end
117
+
98
118
  end
99
119
  end
100
120
 
@@ -89,17 +89,36 @@ describe 'datetime input' do
89
89
  output_buffer.should have_tag('form li.datetime fieldset ol li label', f == field ? /another #{f} label/i : /#{f}/i)
90
90
  end
91
91
  end
92
-
93
- #it "should not display the label for the #{field} field when :labels[:#{field}] is blank" do
94
- # output_buffer.replace ''
95
- # concat(semantic_form_for(@new_post) do |builder|
96
- # concat(builder.input(:created_at, :as => :datetime, :labels => { field => "" }))
97
- # end)
98
- # output_buffer.should have_tag('form li.datetime fieldset ol li label', :count => fields.length-1)
99
- # fields.each do |f|
100
- # output_buffer.should have_tag('form li.datetime fieldset ol li label', /#{f}/i) unless field == f
101
- # end
102
- #end
92
+
93
+ it "should not display the label for the #{field} field when :labels[:#{field}] is blank" do
94
+ output_buffer.replace ''
95
+ concat(semantic_form_for(@new_post) do |builder|
96
+ concat(builder.input(:created_at, :as => :datetime, :labels => { field => "" }))
97
+ end)
98
+ output_buffer.should have_tag('form li.datetime fieldset ol li label', :count => fields.length-1)
99
+ fields.each do |f|
100
+ output_buffer.should have_tag('form li.datetime fieldset ol li label', /#{f}/i) unless field == f
101
+ end
102
+ end
103
+
104
+ it "should not display the label for the #{field} field when :labels[:#{field}] is false" do
105
+ output_buffer.replace ''
106
+ concat(semantic_form_for(@new_post) do |builder|
107
+ concat(builder.input(:created_at, :as => :datetime, :labels => { field => false }))
108
+ end)
109
+ output_buffer.should have_tag('form li.datetime fieldset ol li label', :count => fields.length-1)
110
+ fields.each do |f|
111
+ output_buffer.should have_tag('form li.datetime fieldset ol li label', /#{f}/i) unless field == f
112
+ end
113
+ end
114
+
115
+ it "should not render unsafe HTML when :labels[:#{field}] is false" do
116
+ output_buffer.replace ''
117
+ concat(semantic_form_for(@new_post) do |builder|
118
+ concat(builder.input(:created_at, :as => :time, :include_seconds => true, :labels => { field => false }))
119
+ end)
120
+ output_buffer.should_not include(">")
121
+ end
103
122
  end
104
123
  end
105
124
 
@@ -73,6 +73,14 @@ describe 'Formtastic::FormBuilder#label' do
73
73
  output_buffer.should_not have_tag('label')
74
74
  output_buffer.should_not include(">")
75
75
  end
76
+
77
+ it 'should return nil if label is false for timeish fragments' do
78
+ concat(semantic_form_for(@new_post) do |builder|
79
+ builder.input(:title, :as => :time, :label => false)
80
+ end)
81
+ output_buffer.should_not have_tag('li.time > label')
82
+ output_buffer.should_not include(">")
83
+ end
76
84
 
77
85
  it 'should html escape the label string by default' do
78
86
  concat(semantic_form_for(@new_post) do |builder|
@@ -153,6 +153,84 @@ describe 'number input' do
153
153
 
154
154
  end
155
155
 
156
+ describe "when validations require a minimum value (:greater_than) that takes a proc" do
157
+ before do
158
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
159
+ active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :greater_than=> Proc.new {|post| 2}})
160
+ ])
161
+ end
162
+
163
+ it "should allow :input_html to override :min" do
164
+ concat(semantic_form_for(@new_post) do |builder|
165
+ builder.input(:title, :as => :number, :input_html => { :min => 5 })
166
+ end)
167
+ output_buffer.should have_tag('input[@min="5"]')
168
+ end
169
+
170
+ it "should allow :input_html to override :min through :in" do
171
+ concat(semantic_form_for(@new_post) do |builder|
172
+ builder.input(:title, :as => :number, :input_html => { :in => 5..102 })
173
+ end)
174
+ output_buffer.should have_tag('input[@min="5"]')
175
+ end
176
+
177
+ it "should allow options to override :min" do
178
+ concat(semantic_form_for(@new_post) do |builder|
179
+ builder.input(:title, :as => :number, :min => 5)
180
+ end)
181
+ output_buffer.should have_tag('input[@min="5"]')
182
+ end
183
+
184
+ it "should allow options to override :min through :in" do
185
+ concat(semantic_form_for(@new_post) do |builder|
186
+ builder.input(:title, :as => :number, :in => 5..102)
187
+ end)
188
+ output_buffer.should have_tag('input[@min="5"]')
189
+ end
190
+
191
+ describe "and the column is an integer" do
192
+ before do
193
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :integer))
194
+ end
195
+
196
+ it "should add a min attribute to the input one greater than the validation" do
197
+ concat(semantic_form_for(@new_post) do |builder|
198
+ builder.input(:title, :as => :number)
199
+ end)
200
+ output_buffer.should have_tag('input[@min="3"]')
201
+ end
202
+ end
203
+
204
+ describe "and the column is a float" do
205
+ before do
206
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :float))
207
+ end
208
+
209
+ it "should raise an error" do
210
+ lambda {
211
+ concat(semantic_form_for(@new_post) do |builder|
212
+ builder.input(:title, :as => :number)
213
+ end)
214
+ }.should raise_error(Formtastic::Inputs::Base::Validations::IndeterminableMinimumAttributeError)
215
+ end
216
+ end
217
+
218
+ describe "and the column is a big decimal" do
219
+ before do
220
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :decimal))
221
+ end
222
+
223
+ it "should raise an error" do
224
+ lambda {
225
+ concat(semantic_form_for(@new_post) do |builder|
226
+ builder.input(:title, :as => :number)
227
+ end)
228
+ }.should raise_error(Formtastic::Inputs::Base::Validations::IndeterminableMinimumAttributeError)
229
+ end
230
+ end
231
+
232
+ end
233
+
156
234
  describe "when validations require a minimum value (:greater_than_or_equal_to)" do
157
235
  before do
158
236
  @new_post.class.stub!(:validators_on).with(:title).and_return([
@@ -217,8 +295,74 @@ describe 'number input' do
217
295
  end
218
296
  end
219
297
  end
298
+
299
+ describe "when validations require a minimum value (:greater_than_or_equal_to) that takes a Proc" do
300
+ before do
301
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
302
+ active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :greater_than_or_equal_to=> Proc.new { |post| 2}})
303
+ ])
304
+ end
305
+
306
+ it "should allow :input_html to override :min" do
307
+ concat(semantic_form_for(@new_post) do |builder|
308
+ builder.input(:title, :as => :number, :input_html => { :min => 5 })
309
+ end)
310
+ output_buffer.should have_tag('input[@min="5"]')
311
+ end
312
+
313
+ it "should allow options to override :min" do
314
+ concat(semantic_form_for(@new_post) do |builder|
315
+ builder.input(:title, :as => :number, :min => 5)
316
+ end)
317
+ output_buffer.should have_tag('input[@min="5"]')
318
+ end
319
+
320
+ it "should allow :input_html to override :min with :in" do
321
+ concat(semantic_form_for(@new_post) do |builder|
322
+ builder.input(:title, :as => :number, :input_html => { :in => 5..102 })
323
+ end)
324
+ output_buffer.should have_tag('input[@min="5"]')
325
+ end
326
+
327
+ it "should allow options to override :min with :in" do
328
+ concat(semantic_form_for(@new_post) do |builder|
329
+ builder.input(:title, :as => :number, :in => 5..102)
330
+ end)
331
+ output_buffer.should have_tag('input[@min="5"]')
332
+ end
333
+
334
+
335
+ [:integer, :decimal, :float].each do |column_type|
336
+ describe "and the column is a #{column_type}" do
337
+ before do
338
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => column_type))
339
+ end
340
+
341
+ it "should add a max attribute to the input equal to the validation" do
342
+ concat(semantic_form_for(@new_post) do |builder|
343
+ builder.input(:title, :as => :number)
344
+ end)
345
+ output_buffer.should have_tag('input[@min="2"]')
346
+ end
347
+ end
348
+ end
349
+
350
+ describe "and there is no column" do
351
+ before do
352
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(nil)
353
+ end
354
+
355
+ it "should add a max attribute to the input equal to the validation" do
356
+ concat(semantic_form_for(@new_post) do |builder|
357
+ builder.input(:title, :as => :number)
358
+ end)
359
+ output_buffer.should have_tag('input[@min="2"]')
360
+ end
361
+ end
362
+ end
220
363
 
221
364
  describe "when validations require a maximum value (:less_than)" do
365
+
222
366
  before do
223
367
  @new_post.class.stub!(:validators_on).with(:title).and_return([
224
368
  active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :less_than=>20})
@@ -293,9 +437,97 @@ describe 'number input' do
293
437
  }.should raise_error(Formtastic::Inputs::Base::Validations::IndeterminableMaximumAttributeError)
294
438
  end
295
439
  end
440
+ describe "and the validator takes a proc" do
441
+ before do
442
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :decimal))
443
+ end
444
+ end
445
+ end
446
+
447
+ describe "when validations require a maximum value (:less_than) that takes a Proc" do
448
+
449
+ before do
450
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
451
+ active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :less_than=> Proc.new {|post| 20 }})
452
+ ])
453
+ end
454
+
455
+ it "should allow :input_html to override :max" do
456
+ concat(semantic_form_for(@new_post) do |builder|
457
+ builder.input(:title, :as => :number, :input_html => { :max => 102 })
458
+ end)
459
+ output_buffer.should have_tag('input[@max="102"]')
460
+ end
461
+
462
+ it "should allow option to override :max" do
463
+ concat(semantic_form_for(@new_post) do |builder|
464
+ builder.input(:title, :as => :number, :max => 102)
465
+ end)
466
+ output_buffer.should have_tag('input[@max="102"]')
467
+ end
468
+
469
+ it "should allow :input_html to override :max with :in" do
470
+ concat(semantic_form_for(@new_post) do |builder|
471
+ builder.input(:title, :as => :number, :input_html => { :in => 1..102 })
472
+ end)
473
+ output_buffer.should have_tag('input[@max="102"]')
474
+ end
475
+
476
+ it "should allow option to override :max with :in" do
477
+ concat(semantic_form_for(@new_post) do |builder|
478
+ builder.input(:title, :as => :number, :in => 1..102)
479
+ end)
480
+ output_buffer.should have_tag('input[@max="102"]')
481
+ end
482
+
483
+ describe "and the column is an integer" do
484
+ before do
485
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :integer))
486
+ end
487
+
488
+ it "should add a max attribute to the input one greater than the validation" do
489
+ concat(semantic_form_for(@new_post) do |builder|
490
+ builder.input(:title, :as => :number)
491
+ end)
492
+ output_buffer.should have_tag('input[@max="19"]')
493
+ end
494
+ end
495
+
496
+ describe "and the column is a float" do
497
+ before do
498
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :float))
499
+ end
500
+
501
+ it "should raise an error" do
502
+ lambda {
503
+ concat(semantic_form_for(@new_post) do |builder|
504
+ builder.input(:title, :as => :number)
505
+ end)
506
+ }.should raise_error(Formtastic::Inputs::Base::Validations::IndeterminableMaximumAttributeError)
507
+ end
508
+ end
296
509
 
510
+ describe "and the column is a big decimal" do
511
+ before do
512
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :decimal))
513
+ end
514
+
515
+ it "should raise an error" do
516
+ lambda {
517
+ concat(semantic_form_for(@new_post) do |builder|
518
+ builder.input(:title, :as => :number)
519
+ end)
520
+ }.should raise_error(Formtastic::Inputs::Base::Validations::IndeterminableMaximumAttributeError)
521
+ end
522
+ end
523
+ describe "and the validator takes a proc" do
524
+ before do
525
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :decimal))
526
+ end
527
+ end
297
528
  end
298
529
 
530
+
299
531
  describe "when validations require a maximum value (:less_than_or_equal_to)" do
300
532
  before do
301
533
  @new_post.class.stub!(:validators_on).with(:title).and_return([
@@ -359,6 +591,70 @@ describe 'number input' do
359
591
  end
360
592
  end
361
593
  end
594
+
595
+ describe "when validations require a maximum value (:less_than_or_equal_to) that takes a proc" do
596
+ before do
597
+ @new_post.class.stub!(:validators_on).with(:title).and_return([
598
+ active_model_numericality_validator([:title], {:only_integer=>false, :allow_nil=>false, :less_than_or_equal_to=> Proc.new { |post| 20 }})
599
+ ])
600
+ end
601
+
602
+ it "should allow :input_html to override :max" do
603
+ concat(semantic_form_for(@new_post) do |builder|
604
+ builder.input(:title, :as => :number, :input_html => { :max => 102 })
605
+ end)
606
+ output_buffer.should have_tag('input[@max="102"]')
607
+ end
608
+
609
+ it "should allow options to override :max" do
610
+ concat(semantic_form_for(@new_post) do |builder|
611
+ builder.input(:title, :as => :number, :max => 102)
612
+ end)
613
+ output_buffer.should have_tag('input[@max="102"]')
614
+ end
615
+
616
+ it "should allow :input_html to override :max with :in" do
617
+ concat(semantic_form_for(@new_post) do |builder|
618
+ builder.input(:title, :as => :number, :input_html => { :in => 1..102 })
619
+ end)
620
+ output_buffer.should have_tag('input[@max="102"]')
621
+ end
622
+
623
+ it "should allow options to override :max with :in" do
624
+ concat(semantic_form_for(@new_post) do |builder|
625
+ builder.input(:title, :as => :number, :in => 1..102)
626
+ end)
627
+ output_buffer.should have_tag('input[@max="102"]')
628
+ end
629
+
630
+ [:integer, :decimal, :float].each do |column_type|
631
+ describe "and the column is a #{column_type}" do
632
+ before do
633
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => column_type))
634
+ end
635
+
636
+ it "should add a max attribute to the input equal to the validation" do
637
+ concat(semantic_form_for(@new_post) do |builder|
638
+ builder.input(:title, :as => :number)
639
+ end)
640
+ output_buffer.should have_tag('input[@max="20"]')
641
+ end
642
+ end
643
+ end
644
+
645
+ describe "and there is no column" do
646
+ before do
647
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(nil)
648
+ end
649
+
650
+ it "should add a max attribute to the input equal to the validation" do
651
+ concat(semantic_form_for(@new_post) do |builder|
652
+ builder.input(:title, :as => :number)
653
+ end)
654
+ output_buffer.should have_tag('input[@max="20"]')
655
+ end
656
+ end
657
+ end
362
658
 
363
659
  describe "when validations require conflicting minimum values (:greater_than, :greater_than_or_equal_to)" do
364
660
  before do
@@ -460,11 +756,11 @@ describe 'number input' do
460
756
  ])
461
757
  end
462
758
 
463
- it "should default step to 1" do
759
+ it "should default step to 'any'" do
464
760
  concat(semantic_form_for(@new_post) do |builder|
465
761
  builder.input(:title, :as => :number)
466
762
  end)
467
- output_buffer.should have_tag('input[@step="1"]')
763
+ output_buffer.should have_tag('input[@step="any"]')
468
764
  end
469
765
 
470
766
  it "should let input_html set :step" do