formtastic 2.0.0.rc3 → 2.0.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.travis.yml +7 -0
  2. data/CHANGELOG +26 -0
  3. data/Gemfile +2 -0
  4. data/README.textile +0 -1
  5. data/RELEASE_PROCESS +0 -1
  6. data/Rakefile +1 -1
  7. data/app/assets/stylesheets/formtastic.css +3 -3
  8. data/app/assets/stylesheets/formtastic_ie6.css +7 -1
  9. data/app/assets/stylesheets/formtastic_ie7.css +7 -1
  10. data/formtastic.gemspec +0 -15
  11. data/lib/formtastic/form_builder.rb +2 -0
  12. data/lib/formtastic/helpers/errors_helper.rb +22 -0
  13. data/lib/formtastic/helpers/form_helper.rb +18 -16
  14. data/lib/formtastic/helpers/input_helper.rb +9 -7
  15. data/lib/formtastic/helpers/inputs_helper.rb +11 -3
  16. data/lib/formtastic/helpers/reflection.rb +5 -1
  17. data/lib/formtastic/inputs/base/collections.rb +7 -0
  18. data/lib/formtastic/inputs/base/html.rb +1 -1
  19. data/lib/formtastic/inputs/base/timeish.rb +7 -2
  20. data/lib/formtastic/inputs/base/validations.rb +39 -8
  21. data/lib/formtastic/inputs/check_boxes_input.rb +3 -3
  22. data/lib/formtastic/inputs/file_input.rb +4 -4
  23. data/lib/formtastic/inputs/number_input.rb +1 -1
  24. data/lib/formtastic/inputs/radio_input.rb +1 -1
  25. data/lib/formtastic/inputs/time_input.rb +25 -3
  26. data/lib/formtastic/version.rb +1 -1
  27. data/lib/generators/templates/formtastic.rb +10 -1
  28. data/spec/builder/errors_spec.rb +10 -0
  29. data/spec/builder/semantic_fields_for_spec.rb +77 -36
  30. data/spec/helpers/form_helper_spec.rb +32 -0
  31. data/spec/helpers/input_helper_spec.rb +196 -102
  32. data/spec/helpers/inputs_helper_spec.rb +85 -73
  33. data/spec/helpers/reflection_helper_spec.rb +32 -0
  34. data/spec/inputs/check_boxes_input_spec.rb +21 -6
  35. data/spec/inputs/date_input_spec.rb +20 -0
  36. data/spec/inputs/datetime_input_spec.rb +30 -11
  37. data/spec/inputs/label_spec.rb +8 -0
  38. data/spec/inputs/number_input_spec.rb +298 -2
  39. data/spec/inputs/radio_input_spec.rb +5 -6
  40. data/spec/inputs/string_input_spec.rb +22 -5
  41. data/spec/inputs/time_input_spec.rb +51 -7
  42. data/spec/spec_helper.rb +64 -12
  43. metadata +11 -9
@@ -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