formtastic 3.1.3 → 3.1.4

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