formtastic 3.1.3 → 3.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.travis.yml +12 -10
  2. data/Appraisals +11 -7
  3. data/CHANGELOG +12 -0
  4. data/DEPRECATIONS +3 -0
  5. data/{README.textile → README.md} +629 -616
  6. data/formtastic.gemspec +3 -3
  7. data/gemfiles/rails_3.2.gemfile +1 -0
  8. data/gemfiles/rails_edge.gemfile +5 -0
  9. data/lib/formtastic.rb +4 -0
  10. data/lib/formtastic/form_builder.rb +3 -0
  11. data/lib/formtastic/helpers.rb +1 -1
  12. data/lib/formtastic/helpers/enum.rb +13 -0
  13. data/lib/formtastic/helpers/fieldset_wrapper.rb +6 -6
  14. data/lib/formtastic/helpers/form_helper.rb +1 -1
  15. data/lib/formtastic/helpers/input_helper.rb +5 -1
  16. data/lib/formtastic/helpers/inputs_helper.rb +16 -20
  17. data/lib/formtastic/inputs/base/choices.rb +1 -1
  18. data/lib/formtastic/inputs/base/collections.rb +41 -4
  19. data/lib/formtastic/inputs/base/html.rb +7 -6
  20. data/lib/formtastic/inputs/base/naming.rb +4 -4
  21. data/lib/formtastic/inputs/base/options.rb +2 -3
  22. data/lib/formtastic/inputs/base/validations.rb +19 -3
  23. data/lib/formtastic/inputs/check_boxes_input.rb +10 -2
  24. data/lib/formtastic/inputs/country_input.rb +3 -1
  25. data/lib/formtastic/inputs/radio_input.rb +20 -0
  26. data/lib/formtastic/inputs/select_input.rb +28 -0
  27. data/lib/formtastic/inputs/time_zone_input.rb +16 -6
  28. data/lib/formtastic/localizer.rb +15 -15
  29. data/lib/formtastic/namespaced_class_finder.rb +1 -1
  30. data/lib/formtastic/version.rb +1 -1
  31. data/lib/generators/formtastic/form/form_generator.rb +1 -1
  32. data/lib/generators/formtastic/input/input_generator.rb +46 -0
  33. data/lib/generators/templates/formtastic.rb +10 -7
  34. data/lib/generators/templates/input.rb +19 -0
  35. data/spec/fast_spec_helper.rb +12 -0
  36. data/spec/generators/formtastic/input/input_generator_spec.rb +124 -0
  37. data/spec/helpers/form_helper_spec.rb +4 -4
  38. data/spec/inputs/base/collections_spec.rb +76 -0
  39. data/spec/inputs/base/validations_spec.rb +342 -0
  40. data/spec/inputs/check_boxes_input_spec.rb +66 -20
  41. data/spec/inputs/country_input_spec.rb +4 -4
  42. data/spec/inputs/radio_input_spec.rb +28 -0
  43. data/spec/inputs/readonly_spec.rb +50 -0
  44. data/spec/inputs/select_input_spec.rb +71 -11
  45. data/spec/inputs/time_zone_input_spec.rb +35 -9
  46. data/spec/spec_helper.rb +2 -30
  47. data/spec/support/shared_examples.rb +69 -0
  48. metadata +23 -12
  49. data/spec/support/deferred_garbage_collection.rb +0 -21
@@ -5,6 +5,11 @@ describe 'check_boxes input' do
5
5
 
6
6
  include FormtasticSpecHelper
7
7
 
8
+ before do
9
+ @output_buffer = ''
10
+ mock_everything
11
+ end
12
+
8
13
  describe 'for a has_many association' do
9
14
  before do
10
15
  @output_buffer = ''
@@ -56,15 +61,15 @@ describe 'check_boxes input' do
56
61
  end
57
62
 
58
63
  describe "each choice" do
59
-
64
+
60
65
  it 'should not give the choice label the .label class' do
61
66
  output_buffer.should_not have_tag('li.choice label.label')
62
67
  end
63
-
68
+
64
69
  it 'should not be marked as required' do
65
70
  output_buffer.should_not have_tag('li.choice input[@required]')
66
71
  end
67
-
72
+
68
73
  it 'should contain a label for the radio input with a nested input and label text' do
69
74
  ::Post.all.each do |post|
70
75
  output_buffer.should have_tag('form li fieldset ol li label', /#{post.to_label}/)
@@ -77,7 +82,7 @@ describe 'check_boxes input' do
77
82
  output_buffer.should have_tag("form li fieldset ol li.post_#{post.id} label")
78
83
  end
79
84
  end
80
-
85
+
81
86
  it 'should have a checkbox input but no hidden field for each post' do
82
87
  ::Post.all.each do |post|
83
88
  output_buffer.should have_tag("form li fieldset ol li label input#author_post_ids_#{post.id}")
@@ -290,11 +295,11 @@ describe 'check_boxes input' do
290
295
  it "should not output the legend" do
291
296
  output_buffer.should_not have_tag("legend.label")
292
297
  end
293
-
298
+
294
299
  it "should not cause escaped HTML" do
295
300
  output_buffer.should_not include(">")
296
301
  end
297
-
302
+
298
303
  end
299
304
 
300
305
  describe "when :required option is true" do
@@ -309,7 +314,23 @@ describe 'check_boxes input' do
309
314
  output_buffer.should have_tag("legend.label label abbr")
310
315
  end
311
316
  end
317
+ end
318
+
319
+ describe 'for a enum column' do
320
+ before do
321
+ @new_post.stub(:status) { 'inactive' }
322
+ statuses = ActiveSupport::HashWithIndifferentAccess.new("active"=>0, "inactive"=>1)
323
+ @new_post.class.stub(:statuses) { statuses }
324
+ @new_post.stub(:defined_enums) { { "status" => statuses } }
325
+ end
312
326
 
327
+ it 'should have a select inside the wrapper' do
328
+ expect {
329
+ concat(semantic_form_for(@new_post) do |builder|
330
+ concat(builder.input(:status, :as => :check_boxes))
331
+ end)
332
+ }.to raise_error(Formtastic::UnsupportedEnumCollection)
333
+ end
313
334
  end
314
335
 
315
336
  describe 'for a has_and_belongs_to_many association' do
@@ -337,6 +358,31 @@ describe 'check_boxes input' do
337
358
 
338
359
  end
339
360
 
361
+ describe ':collection for a has_and_belongs_to_many association' do
362
+
363
+ before do
364
+ @output_buffer = ''
365
+ mock_everything
366
+
367
+ concat(semantic_form_for(@freds_post) do |builder|
368
+ concat(builder.input(:authors, as: :check_boxes, collection: Author.all))
369
+ end)
370
+ end
371
+
372
+ it 'should render checkboxes' do
373
+ # I'm aware these two lines test the same thing
374
+ output_buffer.should have_tag('input[type="checkbox"]', :count => 2)
375
+ output_buffer.should have_tag('input[type="checkbox"]', :count => ::Author.all.size)
376
+ end
377
+
378
+ it 'should only select checkboxes that are present in the association' do
379
+ # I'm aware these two lines test the same thing
380
+ output_buffer.should have_tag('input[checked="checked"]', :count => 1)
381
+ output_buffer.should have_tag('input[checked="checked"]', :count => @freds_post.authors.size)
382
+ end
383
+
384
+ end
385
+
340
386
  describe 'for an association when a :collection is provided' do
341
387
  describe 'it should use the specified :member_value option' do
342
388
  before do
@@ -350,7 +396,7 @@ describe 'check_boxes input' do
350
396
  item.stub(:custom_value).and_return('custom_value')
351
397
  item.should_receive(:custom_value).exactly(3).times
352
398
  @new_post.author.should_receive(:custom_value).exactly(1).times
353
-
399
+
354
400
  with_deprecation_silenced do
355
401
  concat(semantic_form_for(@new_post) do |builder|
356
402
  concat(builder.input(:author, :as => :check_boxes, :member_value => :custom_value, :collection => [item, item, item]))
@@ -360,35 +406,35 @@ describe 'check_boxes input' do
360
406
  end
361
407
  end
362
408
  end
363
-
409
+
364
410
  describe 'when :collection is provided as an array of arrays' do
365
411
  before do
366
412
  @output_buffer = ''
367
413
  mock_everything
368
414
  @fred.stub(:genres) { ['fiction', 'biography'] }
369
-
415
+
370
416
  concat(semantic_form_for(@fred) do |builder|
371
417
  concat(builder.input(:genres, :as => :check_boxes, :collection => [['Fiction', 'fiction'], ['Non-fiction', 'non_fiction'], ['Biography', 'biography']]))
372
418
  end)
373
419
  end
374
-
420
+
375
421
  it 'should check the correct checkboxes' do
376
422
  output_buffer.should have_tag("form li fieldset ol li label input[@value='fiction'][@checked='checked']")
377
423
  output_buffer.should have_tag("form li fieldset ol li label input[@value='biography'][@checked='checked']")
378
424
  end
379
425
  end
380
-
426
+
381
427
  describe 'when :collection is a set' do
382
428
  before do
383
429
  @output_buffer = ''
384
430
  mock_everything
385
431
  @fred.stub(:roles) { Set.new([:reviewer, :admin]) }
386
-
432
+
387
433
  concat(semantic_form_for(@fred) do |builder|
388
434
  concat(builder.input(:roles, :as => :check_boxes, :collection => [['User', :user], ['Reviewer', :reviewer], ['Administrator', :admin]]))
389
435
  end)
390
436
  end
391
-
437
+
392
438
  it 'should check the correct checkboxes' do
393
439
  output_buffer.should have_tag("form li fieldset ol li label input[@value='user']")
394
440
  output_buffer.should have_tag("form li fieldset ol li label input[@value='admin'][@checked='checked']")
@@ -406,7 +452,7 @@ describe 'check_boxes input' do
406
452
  concat(builder.input(:posts, :as => :check_boxes))
407
453
  end)
408
454
  end
409
-
455
+
410
456
  it "should have a label for #context2_author_post_ids_19" do
411
457
  output_buffer.should have_tag("form li label[@for='context2_author_post_ids_19']")
412
458
  end
@@ -414,7 +460,7 @@ describe 'check_boxes input' do
414
460
  it_should_have_input_with_id('context2_author_post_ids_19')
415
461
  it_should_have_input_wrapper_with_id("context2_author_posts_input")
416
462
  end
417
-
463
+
418
464
  describe "when index is provided" do
419
465
 
420
466
  before do
@@ -427,11 +473,11 @@ describe 'check_boxes input' do
427
473
  end)
428
474
  end)
429
475
  end
430
-
476
+
431
477
  it 'should index the id of the wrapper' do
432
478
  output_buffer.should have_tag("li#author_post_3_authors_input")
433
479
  end
434
-
480
+
435
481
  it 'should index the id of the input tag' do
436
482
  output_buffer.should have_tag("input#author_post_3_author_ids_42")
437
483
  end
@@ -439,9 +485,9 @@ describe 'check_boxes input' do
439
485
  it 'should index the name of the checkbox input' do
440
486
  output_buffer.should have_tag("input[@type='checkbox'][@name='author[post][3][author_ids][]']")
441
487
  end
442
-
488
+
443
489
  end
444
-
490
+
445
491
 
446
492
  describe "when collection is an array" do
447
493
  before do
@@ -495,6 +541,6 @@ describe 'check_boxes input' do
495
541
  end
496
542
  end
497
543
  end
498
-
544
+
499
545
  end
500
546
 
@@ -17,7 +17,7 @@ describe 'country input' do
17
17
  semantic_form_for(@new_post) do |builder|
18
18
  concat(builder.input(:country, :as => :country))
19
19
  end
20
- }.should raise_error
20
+ }.should raise_error(Formtastic::Inputs::CountryInput::CountrySelectPluginMissing)
21
21
  end
22
22
 
23
23
  end
@@ -56,7 +56,7 @@ describe 'country input' do
56
56
  priority_countries = ["Foo", "Bah"]
57
57
  semantic_form_for(@new_post) do |builder|
58
58
  builder.stub(:country_select).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
59
- builder.should_receive(:country_select).with(:country, priority_countries, {}, {:id => "post_country", :required => false, :autofocus => false}).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
59
+ builder.should_receive(:country_select).with(:country, priority_countries, {}, {:id => "post_country", :required => false, :autofocus => false, :readonly => false}).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
60
60
 
61
61
  concat(builder.input(:country, :as => :country, :priority_countries => priority_countries))
62
62
  end
@@ -69,7 +69,7 @@ describe 'country input' do
69
69
 
70
70
  semantic_form_for(@new_post) do |builder|
71
71
  builder.stub(:country_select).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
72
- builder.should_receive(:country_select).with(:country, priority_countries, {}, {:id => "post_country", :required => false, :autofocus => false}).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
72
+ builder.should_receive(:country_select).with(:country, priority_countries, {}, {:id => "post_country", :required => false, :autofocus => false, :readonly => false}).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
73
73
 
74
74
  concat(builder.input(:country, :as => :country))
75
75
  end
@@ -85,7 +85,7 @@ describe 'country input' do
85
85
 
86
86
  concat(semantic_form_for(@new_post, :namespace => 'context2') do |builder|
87
87
  builder.stub(:country_select).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
88
- builder.should_receive(:country_select).with(:country, [], {}, {:id => "context2_post_country", :required => false, :autofocus => false}).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
88
+ builder.should_receive(:country_select).with(:country, [], {}, {:id => "context2_post_country", :required => false, :autofocus => false, :readonly => false}).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
89
89
  concat(builder.input(:country, :priority_countries => []))
90
90
  end)
91
91
  end
@@ -152,6 +152,34 @@ describe 'radio input' do
152
152
  end
153
153
  end
154
154
 
155
+ describe 'for a enum column' do
156
+ before do
157
+ @new_post.stub(:status) { 'inactive' }
158
+ statuses = ActiveSupport::HashWithIndifferentAccess.new("active"=>0, "inactive"=>1)
159
+ @new_post.class.stub(:statuses) { statuses }
160
+ @new_post.stub(:defined_enums) { { "status" => statuses } }
161
+ end
162
+
163
+ before do
164
+ concat(semantic_form_for(@new_post) do |builder|
165
+ concat(builder.input(:status, :as => :radio))
166
+ end)
167
+ end
168
+
169
+ it 'should have a radio input for each defined enum status' do
170
+ output_buffer.should have_tag("form li input[@name='post[status]'][@type='radio']", :count => @new_post.class.statuses.count)
171
+ @new_post.class.statuses.each do |label, value|
172
+ output_buffer.should have_tag("form li input[@value='#{label}']")
173
+ output_buffer.should have_tag("form li label", /#{label.humanize}/)
174
+ end
175
+ end
176
+
177
+ it 'should have one radio input with a "checked" attribute' do
178
+ output_buffer.should have_tag("form li input[@name='post[status]'][@checked]", :count => 1)
179
+ end
180
+ end
181
+
182
+
155
183
  describe "with i18n of the legend label" do
156
184
 
157
185
  before do
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'readonly option' do
5
+
6
+ include FormtasticSpecHelper
7
+
8
+ before do
9
+ @output_buffer = ''
10
+ mock_everything
11
+ end
12
+
13
+ describe "placeholder text" do
14
+ [:email, :number, :password, :phone, :search, :string, :url, :text, :date_picker, :time_picker, :datetime_picker].each do |type|
15
+
16
+ describe "for #{type} inputs" do
17
+
18
+ describe "when readonly is found in input_html" do
19
+ it "sets readonly attribute" do
20
+ concat(semantic_form_for(@new_post) do |builder|
21
+ concat(builder.input(:title, :as => type, input_html: {readonly: true}))
22
+ end)
23
+ output_buffer.should have_tag((type == :text ? 'textarea' : 'input') + '[@readonly]')
24
+ end
25
+ end
26
+
27
+ describe "when readonly not found in input_html" do
28
+ describe "when column is not readonly attribute" do
29
+ it "doesn't set readonly attribute" do
30
+ concat(semantic_form_for(@new_post) do |builder|
31
+ concat(builder.input(:title, :as => type))
32
+ end)
33
+ output_buffer.should_not have_tag((type == :text ? 'textarea' : 'input') + '[@readonly]')
34
+ end
35
+ end
36
+ describe "when column is readonly attribute" do
37
+ it "sets readonly attribute" do
38
+ input_class = "Formtastic::Inputs::#{type.to_s.camelize}Input".constantize
39
+ expect_any_instance_of(input_class).to receive(:readonly_attribute?).at_least(1).and_return(true)
40
+ concat(semantic_form_for(@new_post) do |builder|
41
+ concat(builder.input(:title, :as => type))
42
+ end)
43
+ output_buffer.should have_tag((type == :text ? 'textarea' : 'input') + '[@readonly]')
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -151,6 +151,77 @@ describe 'select input' do
151
151
  end
152
152
  end
153
153
 
154
+ describe 'for a enum column' do
155
+ before do
156
+ @new_post.stub(:status) { 'inactive' }
157
+ statuses = ActiveSupport::HashWithIndifferentAccess.new("active"=>0, "inactive"=>1)
158
+ @new_post.class.stub(:statuses) { statuses }
159
+ @new_post.stub(:defined_enums) { { "status" => statuses } }
160
+ end
161
+
162
+ context 'single choice' do
163
+ before do
164
+ concat(semantic_form_for(@new_post) do |builder|
165
+ concat(builder.input(:status, :as => :select))
166
+ end)
167
+ end
168
+
169
+ it_should_have_input_wrapper_with_class("select")
170
+ it_should_have_input_wrapper_with_class(:input)
171
+ it_should_have_input_wrapper_with_id("post_status_input")
172
+ it_should_have_label_with_text(/Status/)
173
+ it_should_have_label_for('post_status')
174
+ it_should_apply_error_logic_for_input_type(:select)
175
+
176
+ it 'should have a select inside the wrapper' do
177
+ output_buffer.should have_tag('form li select')
178
+ output_buffer.should have_tag('form li select#post_status')
179
+ end
180
+
181
+ it 'should have a valid name' do
182
+ output_buffer.should have_tag("form li select[@name='post[status]']")
183
+ output_buffer.should_not have_tag("form li select[@name='post[status][]']")
184
+ end
185
+
186
+ it 'should not create a multi-select' do
187
+ output_buffer.should_not have_tag('form li select[@multiple]')
188
+ end
189
+
190
+ it 'should not add a hidden input' do
191
+ output_buffer.should_not have_tag('form li input[@type="hidden"]')
192
+ end
193
+
194
+ it 'should create a select without size' do
195
+ output_buffer.should_not have_tag('form li select[@size]')
196
+ end
197
+
198
+ it 'should have a blank option' do
199
+ output_buffer.should have_tag("form li select option[@value='']")
200
+ end
201
+
202
+ it 'should have a select option for each defined enum status' do
203
+ output_buffer.should have_tag("form li select[@name='post[status]'] option", :count => @new_post.class.statuses.count + 1)
204
+ @new_post.class.statuses.each do |label, value|
205
+ output_buffer.should have_tag("form li select option[@value='#{label}']", /#{label.humanize}/)
206
+ end
207
+ end
208
+
209
+ it 'should have one option with a "selected" attribute (TODO)' do
210
+ output_buffer.should have_tag("form li select[@name='post[status]'] option[@selected]", :count => 1)
211
+ end
212
+ end
213
+
214
+ context 'multiple choice' do
215
+ it 'raises an error' do
216
+ expect {
217
+ concat(semantic_form_for(@new_post) do |builder|
218
+ concat(builder.input(:status, :as => :select, :multiple => true))
219
+ end)
220
+ }.to raise_error Formtastic::UnsupportedEnumCollection
221
+ end
222
+ end
223
+ end
224
+
154
225
  describe 'for a belongs_to association' do
155
226
  before do
156
227
  concat(semantic_form_for(@new_post) do |builder|
@@ -240,17 +311,6 @@ describe 'select input' do
240
311
  concat(builder.input(:author, :as => :select))
241
312
  end
242
313
  end
243
-
244
- it "should call author.(scoped|where) with association conditions" do
245
- if Formtastic::Util.rails3?
246
- ::Author.should_receive(:scoped).with(:conditions => {:active => true})
247
- else
248
- ::Author.should_receive(:where).with({:active => true})
249
- end
250
- semantic_form_for(@new_post) do |builder|
251
- concat(builder.input(:author, :as => :select))
252
- end
253
- end
254
314
  end
255
315
 
256
316
  describe 'for a has_many association' do
@@ -54,7 +54,7 @@ describe 'time_zone input' do
54
54
  it_should_have_label_for("context2_post_time_zone")
55
55
 
56
56
  end
57
-
57
+
58
58
  describe "when index is provided" do
59
59
 
60
60
  before do
@@ -67,21 +67,21 @@ describe 'time_zone input' do
67
67
  end)
68
68
  end)
69
69
  end
70
-
70
+
71
71
  it 'should index the id of the wrapper' do
72
72
  output_buffer.should have_tag("li#post_author_attributes_3_name_input")
73
73
  end
74
-
74
+
75
75
  it 'should index the id of the select tag' do
76
76
  output_buffer.should have_tag("select#post_author_attributes_3_name")
77
77
  end
78
-
78
+
79
79
  it 'should index the name of the select tag' do
80
80
  output_buffer.should have_tag("select[@name='post[author_attributes][3][name]']")
81
81
  end
82
-
82
+
83
83
  end
84
-
84
+
85
85
 
86
86
  describe 'when no object is given' do
87
87
  before(:each) do
@@ -102,10 +102,10 @@ describe 'time_zone input' do
102
102
  output_buffer.should have_tag("form li select[@name=\"project[time_zone]\"]")
103
103
  end
104
104
  end
105
-
105
+
106
106
  context "when required" do
107
107
  it "should add the required attribute to the input's html options" do
108
- with_config :use_required_attribute, true do
108
+ with_config :use_required_attribute, true do
109
109
  concat(semantic_form_for(@new_post) do |builder|
110
110
  concat(builder.input(:title, :as => :time_zone, :required => true))
111
111
  end)
@@ -113,5 +113,31 @@ describe 'time_zone input' do
113
113
  end
114
114
  end
115
115
  end
116
-
116
+
117
+ describe "when priority time zone is provided" do
118
+ let(:time_zones) { [ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii']] }
119
+ let(:input_html_options) do
120
+ { id: "post_title", required: false, autofocus: false, readonly: false }
121
+ end
122
+
123
+ context "by priority_zone option" do
124
+ it "passes right time_zones" do
125
+ expect_any_instance_of(Formtastic::FormBuilder).to receive(:time_zone_select).with(:title, time_zones, {}, input_html_options)
126
+ semantic_form_for(@new_post) do |builder|
127
+ builder.input(:title, as: :time_zone, priority_zones: time_zones)
128
+ end
129
+ end
130
+ end
131
+
132
+ context "by configuration" do
133
+ it "passes right time_zones" do
134
+ expect_any_instance_of(Formtastic::FormBuilder).to receive(:time_zone_select).with(:title, time_zones, {}, input_html_options)
135
+ with_config :priority_time_zones, time_zones do
136
+ semantic_form_for(@new_post) do |builder|
137
+ builder.input(:title, as: :time_zone)
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
117
143
  end