formtastic 3.0.0 → 3.1.0.rc1

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 +1 -0
  2. data/Appraisals +4 -0
  3. data/CHANGELOG +12 -22
  4. data/DEPRECATIONS +47 -0
  5. data/README.textile +9 -4
  6. data/formtastic.gemspec +3 -3
  7. data/gemfiles/rails_4.2.gemfile +7 -0
  8. data/lib/formtastic.rb +13 -7
  9. data/lib/formtastic/action_class_finder.rb +18 -0
  10. data/lib/formtastic/deprecation.rb +42 -0
  11. data/lib/formtastic/form_builder.rb +12 -6
  12. data/lib/formtastic/helpers/action_helper.rb +43 -6
  13. data/lib/formtastic/helpers/form_helper.rb +2 -2
  14. data/lib/formtastic/helpers/input_helper.rb +71 -31
  15. data/lib/formtastic/html_attributes.rb +12 -1
  16. data/lib/formtastic/input_class_finder.rb +18 -0
  17. data/lib/formtastic/inputs.rb +1 -0
  18. data/lib/formtastic/inputs/base.rb +11 -12
  19. data/lib/formtastic/inputs/base/choices.rb +1 -1
  20. data/lib/formtastic/inputs/base/collections.rb +1 -1
  21. data/lib/formtastic/inputs/base/html.rb +3 -3
  22. data/lib/formtastic/inputs/datalist_input.rb +41 -0
  23. data/lib/formtastic/inputs/file_input.rb +2 -2
  24. data/lib/formtastic/namespaced_class_finder.rb +89 -0
  25. data/lib/formtastic/util.rb +1 -1
  26. data/lib/formtastic/version.rb +1 -1
  27. data/lib/generators/templates/formtastic.rb +20 -0
  28. data/spec/action_class_finder_spec.rb +12 -0
  29. data/spec/builder/custom_builder_spec.rb +2 -2
  30. data/spec/builder/semantic_fields_for_spec.rb +4 -4
  31. data/spec/helpers/action_helper_spec.rb +9 -355
  32. data/spec/helpers/form_helper_spec.rb +11 -1
  33. data/spec/helpers/input_helper_spec.rb +1 -916
  34. data/spec/helpers/namespaced_action_helper_spec.rb +43 -0
  35. data/spec/helpers/namespaced_input_helper_spec.rb +36 -0
  36. data/spec/input_class_finder_spec.rb +10 -0
  37. data/spec/inputs/check_boxes_input_spec.rb +2 -2
  38. data/spec/inputs/datalist_input_spec.rb +61 -0
  39. data/spec/inputs/select_input_spec.rb +1 -1
  40. data/spec/localizer_spec.rb +2 -2
  41. data/spec/namespaced_class_finder_spec.rb +79 -0
  42. data/spec/spec_helper.rb +17 -7
  43. data/spec/support/custom_macros.rb +22 -4
  44. data/spec/support/shared_examples.rb +1244 -0
  45. data/spec/support/specialized_class_finder_shared_example.rb +27 -0
  46. data/spec/support/test_environment.rb +1 -1
  47. data/spec/util_spec.rb +20 -6
  48. metadata +66 -15
  49. checksums.yaml +0 -15
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'with action class finder' do
4
+ include_context 'form builder'
5
+
6
+ before {
7
+ allow(Formtastic::FormBuilder)
8
+ .to receive(:action_class_finder).and_return(Formtastic::ActionClassFinder)
9
+ }
10
+
11
+ it_behaves_like 'Action Helper'
12
+
13
+ describe 'instantiating an action class' do
14
+ it "should delegate to ActionClassFinder" do
15
+ concat(semantic_form_for(@new_post) do |builder|
16
+ Formtastic::ActionClassFinder.any_instance.should_receive(:find).
17
+ with(:button).and_call_original
18
+
19
+ builder.action(:submit, :as => :button)
20
+ end)
21
+ end
22
+
23
+ describe 'when instantiated multiple times with the same action type' do
24
+ it "should be cached" do
25
+ concat(semantic_form_for(@new_post) do |builder|
26
+ Formtastic::ActionClassFinder.should_receive(:new).once.and_call_original
27
+ builder.action(:submit, :as => :button)
28
+ builder.action(:submit, :as => :button)
29
+ end)
30
+ end
31
+ end
32
+
33
+ context 'of unknown action' do
34
+ it "should try to load class named as the action" do
35
+ expect {
36
+ semantic_form_for(@new_post) do |builder|
37
+ builder.action(:destroy)
38
+ end
39
+ }.to raise_error(Formtastic::UnknownActionError, 'Unable to find action class DestroyAction')
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'with input class finder' do
5
+ include_context 'form builder'
6
+
7
+ before {
8
+ allow(Formtastic::FormBuilder)
9
+ .to receive(:input_class_finder).and_return(Formtastic::InputClassFinder)
10
+ }
11
+ it_behaves_like 'Input Helper' # from spec/support/shared_examples.rb
12
+
13
+
14
+ describe 'instantiating an input class' do
15
+ describe 'when instantiated multiple times with the same input type' do
16
+
17
+ it "should be cached (not calling the internal methods)" do
18
+ # TODO this is really tied to the underlying implementation
19
+ concat(semantic_form_for(@new_post) do |builder|
20
+ Formtastic::InputClassFinder.should_receive(:new).once.and_call_original
21
+ builder.input(:title, :as => :string)
22
+ builder.input(:title, :as => :string)
23
+ end)
24
+ end
25
+ end
26
+
27
+ it "should delegate to InputClassFinder" do
28
+ concat(semantic_form_for(@new_post) do |builder|
29
+ Formtastic::InputClassFinder.any_instance.should_receive(:find).
30
+ with(:string).and_call_original
31
+
32
+ builder.input(:title, :as => :string)
33
+ end)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'formtastic/input_class_finder'
4
+
5
+ describe Formtastic::InputClassFinder do
6
+ it_behaves_like 'Specialized Class Finder' do
7
+ let(:default) { Formtastic::Inputs }
8
+ let(:namespaces_setting) { :input_namespaces }
9
+ end
10
+ end
@@ -113,7 +113,7 @@ describe 'check_boxes input' do
113
113
  end
114
114
 
115
115
  it "should mark input as checked if it's the the existing choice" do
116
- ::Post.all.include?(@fred.posts.first).should be_true
116
+ ::Post.all.include?(@fred.posts.first).should be_truthy
117
117
  output_buffer.should have_tag("form li fieldset ol li label input[@checked='checked']")
118
118
  end
119
119
  end
@@ -179,7 +179,7 @@ describe 'check_boxes input' do
179
179
  end
180
180
 
181
181
  it "should mark input as checked if it's the the existing choice" do
182
- ::Post.all.include?(@fred.posts.first).should be_true
182
+ ::Post.all.include?(@fred.posts.first).should be_truthy
183
183
  output_buffer.should have_tag("form li fieldset ol li label input[@checked='checked']")
184
184
  end
185
185
 
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe "datalist inputs" do
5
+ include FormtasticSpecHelper
6
+
7
+ before do
8
+ @output_buffer = ''
9
+ mock_everything
10
+ end
11
+
12
+ describe "renders correctly" do
13
+ lists_without_values =[
14
+ %w(a b c),
15
+ ["a", "b", "c"],
16
+ ("a".."c")
17
+ ]
18
+ lists_with_values = [
19
+ {a: 1, b: 2, c:3},
20
+ {"a" => 1, "b" => 2, "c" =>3},
21
+ [["a",1], ["b",2], ["c", 3]]
22
+ ]
23
+
24
+ def self.common_tests(list)
25
+ it_should_have_label_with_text(/Document/)
26
+ it_should_have_label_for("post_document")
27
+ it_should_have_input_wrapper_with_class(:datalist)
28
+ it_should_have_input_with(id: "post_document", type: :text, list:"post_document_datalist")
29
+ it_should_have_tag_with(:datalist, id: "post_document_datalist" )
30
+ it_should_have_many_tags(:option, list.count)
31
+ end
32
+
33
+ context "Rendering list of simple items" do
34
+ lists_without_values.each do |list|
35
+ describe "renders #{list.to_s} correctly" do
36
+ before do
37
+ concat(semantic_form_for(@new_post) do |builder|
38
+ concat(builder.input(:document, as: :datalist, collection: list))
39
+ end)
40
+ end
41
+ common_tests list
42
+ it_should_have_tag_with :option, value: list.first
43
+ end
44
+ end
45
+ end
46
+
47
+ context "Rendering list of complex items, key-value pairs and such" do
48
+ lists_with_values.each do |list|
49
+ describe "renders #{list.to_s} correctly" do
50
+ before do
51
+ concat(semantic_form_for(@new_post) do |builder|
52
+ concat(builder.input(:document, as: :datalist, collection: list))
53
+ end)
54
+ end
55
+ common_tests list
56
+ it_should_have_tag_with :option, value: list.first.last
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -222,7 +222,7 @@ describe 'select input' do
222
222
 
223
223
  describe "for a belongs_to association with :conditions" do
224
224
  before do
225
- ::Post.stub(:reflect_on_association).with(:author).and_return do
225
+ ::Post.stub(:reflect_on_association).with(:author) do
226
226
  mock = double('reflection', :options => {:conditions => {:active => true}}, :klass => ::Author, :macro => :belongs_to)
227
227
  mock.stub(:[]).with(:class_name).and_return("Author")
228
228
  mock
@@ -16,8 +16,8 @@ describe 'Formtastic::Localizer' do
16
16
  end
17
17
 
18
18
  it "should check if key exists?" do
19
- @cache.has_key?(@key).should be_true
20
- @cache.has_key?(@undefined_key).should be_false
19
+ @cache.has_key?(@key).should be_truthy
20
+ @cache.has_key?(@undefined_key).should be_falsey
21
21
  end
22
22
 
23
23
  it "should set a key" do
@@ -0,0 +1,79 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'formtastic/namespaced_class_finder'
4
+
5
+ describe Formtastic::NamespacedClassFinder do
6
+ include FormtasticSpecHelper
7
+
8
+ before do
9
+ stub_const('SearchPath', Module.new)
10
+ end
11
+
12
+ let(:search_path) { [ SearchPath ] }
13
+ subject(:finder) { Formtastic::NamespacedClassFinder.new(search_path) }
14
+
15
+ shared_examples 'Namespaced Class Finder' do
16
+ subject(:found_class) { finder.find(:custom_class) }
17
+
18
+ context 'Input defined in the Object scope' do
19
+ before do
20
+ stub_const('CustomClass', Class.new)
21
+ end
22
+
23
+ it { expect(found_class).to be(CustomClass) }
24
+ end
25
+
26
+ context 'Input defined in the search path' do
27
+ before do
28
+ stub_const('SearchPath::CustomClass', Class.new)
29
+ end
30
+
31
+ it { expect(found_class).to be(SearchPath::CustomClass) }
32
+ end
33
+
34
+ context 'Input defined both in the Object scope and the search path' do
35
+ before do
36
+ stub_const('CustomClass', Class.new)
37
+ stub_const('SearchPath::CustomClass', Class.new)
38
+ end
39
+
40
+ it { expect(found_class).to be(SearchPath::CustomClass) }
41
+ end
42
+
43
+ context 'Input defined outside the search path' do
44
+ before do
45
+ stub_const('Foo', Module.new)
46
+ stub_const('Foo::CustomClass', Class.new)
47
+ end
48
+
49
+ let(:error) { Formtastic::NamespacedClassFinder::NotFoundError }
50
+
51
+ it { expect { found_class }.to raise_error(error) }
52
+ end
53
+ end
54
+
55
+ context '#finder' do
56
+ before do
57
+ Rails.application.config.stub(:cache_classes).and_return(cache_classes)
58
+ end
59
+
60
+ context 'when cache_classes is on' do
61
+ let(:cache_classes) { true }
62
+ it_behaves_like 'Namespaced Class Finder'
63
+ end
64
+
65
+ context 'when cache_classes is off' do
66
+ let(:cache_classes) { false }
67
+ it_behaves_like 'Namespaced Class Finder'
68
+ end
69
+ end
70
+
71
+ context '#find' do
72
+ it 'caches calls' do
73
+ expect(subject).to receive(:resolve).once.and_call_original
74
+ subject.find(:object)
75
+ subject.find(:object)
76
+ end
77
+ end
78
+
79
+ end
@@ -151,7 +151,7 @@ module FormtasticSpecHelper
151
151
 
152
152
  ##
153
153
  # We can't mock :respond_to?, so we need a concrete class override
154
- class ::MongoidReflectionMock < RSpec::Mocks::Mock
154
+ class ::MongoidReflectionMock < RSpec::Mocks::Double
155
155
  def initialize(name=nil, stubs_and_options={})
156
156
  super name, stubs_and_options
157
157
  end
@@ -300,9 +300,9 @@ module FormtasticSpecHelper
300
300
  ::Author.stub(:find).and_return(author_array_or_scope)
301
301
  ::Author.stub(:all).and_return(author_array_or_scope)
302
302
  ::Author.stub(:where).and_return(author_array_or_scope)
303
- ::Author.stub(:human_attribute_name).and_return { |column_name| column_name.humanize }
303
+ ::Author.stub(:human_attribute_name) { |column_name| column_name.humanize }
304
304
  ::Author.stub(:human_name).and_return('::Author')
305
- ::Author.stub(:reflect_on_association).and_return { |column_name| double('reflection', :options => {}, :klass => Post, :macro => :has_many) if column_name == :posts }
305
+ ::Author.stub(:reflect_on_association) { |column_name| double('reflection', :options => {}, :klass => Post, :macro => :has_many) if column_name == :posts }
306
306
  ::Author.stub(:content_columns).and_return([double('column', :name => 'login'), double('column', :name => 'created_at')])
307
307
  ::Author.stub(:to_key).and_return(nil)
308
308
  ::Author.stub(:persisted?).and_return(nil)
@@ -325,6 +325,7 @@ module FormtasticSpecHelper
325
325
  @new_post.stub(:to_key).and_return(nil)
326
326
  @new_post.stub(:to_model).and_return(@new_post)
327
327
  @new_post.stub(:persisted?).and_return(nil)
328
+ @new_post.stub(:model_name){ @new_post.class.model_name}
328
329
 
329
330
  @freds_post = double('post')
330
331
  @freds_post.stub(:to_ary)
@@ -340,16 +341,18 @@ module FormtasticSpecHelper
340
341
  @freds_post.stub(:errors).and_return(double('errors', :[] => nil))
341
342
  @freds_post.stub(:to_key).and_return(nil)
342
343
  @freds_post.stub(:persisted?).and_return(nil)
344
+ @freds_post.stub(:model_name){ @freds_post.class.model_name}
345
+ @freds_post.stub(:to_model).and_return(@freds_post)
343
346
  @fred.stub(:posts).and_return(author_array_or_scope([@freds_post]))
344
347
  @fred.stub(:post_ids).and_return([@freds_post.id])
345
348
 
346
349
  ::Post.stub(:scoped).and_return(::Post)
347
- ::Post.stub(:human_attribute_name).and_return { |column_name| column_name.humanize }
350
+ ::Post.stub(:human_attribute_name) { |column_name| column_name.humanize }
348
351
  ::Post.stub(:human_name).and_return('Post')
349
352
  ::Post.stub(:reflect_on_all_validations).and_return([])
350
353
  ::Post.stub(:reflect_on_validations_for).and_return([])
351
354
  ::Post.stub(:reflections).and_return({})
352
- ::Post.stub(:reflect_on_association).and_return do |column_name|
355
+ ::Post.stub(:reflect_on_association) { |column_name|
353
356
  case column_name
354
357
  when :author, :author_status
355
358
  mock = double('reflection', :options => {}, :klass => ::Author, :macro => :belongs_to)
@@ -370,7 +373,7 @@ module FormtasticSpecHelper
370
373
  :options => Proc.new { raise NoMethodError, "Mongoid has no reflection.options" },
371
374
  :klass => ::Author, :macro => :referenced_in, :foreign_key => "reviewer_id") # custom id
372
375
  end
373
- end
376
+ }
374
377
  ::Post.stub(:find).and_return(author_array_or_scope([@freds_post]))
375
378
  ::Post.stub(:all).and_return(author_array_or_scope([@freds_post]))
376
379
  ::Post.stub(:where).and_return(author_array_or_scope([@freds_post]))
@@ -379,7 +382,7 @@ module FormtasticSpecHelper
379
382
  ::Post.stub(:persisted?).and_return(nil)
380
383
  ::Post.stub(:to_ary)
381
384
 
382
- ::MongoPost.stub(:human_attribute_name).and_return { |column_name| column_name.humanize }
385
+ ::MongoPost.stub(:human_attribute_name) { |column_name| column_name.humanize }
383
386
  ::MongoPost.stub(:human_name).and_return('MongoPost')
384
387
  ::MongoPost.stub(:associations).and_return({
385
388
  :sub_posts => double('reflection', :options => {:polymorphic => true}, :klass => ::MongoPost, :macro => :has_many),
@@ -403,6 +406,7 @@ module FormtasticSpecHelper
403
406
  @new_mm_post.stub(:to_key).and_return(nil)
404
407
  @new_mm_post.stub(:to_model).and_return(@new_mm_post)
405
408
  @new_mm_post.stub(:persisted?).and_return(nil)
409
+ @new_mm_post.stub(:model_name).and_return(::MongoPost.model_name)
406
410
 
407
411
  @mock_file = double('file')
408
412
  Formtastic::FormBuilder.file_methods.each do |method|
@@ -521,6 +525,12 @@ end
521
525
  ::ActiveSupport::Deprecation.silenced = false
522
526
 
523
527
  RSpec.configure do |config|
528
+ config.infer_spec_type_from_file_location!
529
+
530
+ config.filter_run focus: true
531
+ config.filter_run_excluding skip: true
532
+ config.run_all_when_everything_filtered = true
533
+
524
534
  config.before(:each) do
525
535
  Formtastic::Localizer.cache.clear!
526
536
  end
@@ -31,13 +31,13 @@ module CustomMacros
31
31
  output_buffer.should have_tag("form li fieldset")
32
32
  end
33
33
  end
34
-
34
+
35
35
  def it_should_have_a_nested_fieldset_with_class(klass)
36
36
  it "should have a nested_fieldset with class #{klass}" do
37
37
  output_buffer.should have_tag("form li fieldset.#{klass}")
38
38
  end
39
39
  end
40
-
40
+
41
41
  def it_should_have_a_nested_ordered_list_with_class(klass)
42
42
  it "should have a nested fieldset with class #{klass}" do
43
43
  output_buffer.should have_tag("form li ol.#{klass}")
@@ -55,7 +55,7 @@ module CustomMacros
55
55
  output_buffer.should have_tag("form li label.label[@for='#{element_id}']")
56
56
  end
57
57
  end
58
-
58
+
59
59
  def it_should_have_an_inline_label_for(element_id)
60
60
  it "should have a label for ##{element_id}" do
61
61
  output_buffer.should have_tag("form li label[@for='#{element_id}']")
@@ -74,6 +74,24 @@ module CustomMacros
74
74
  end
75
75
  end
76
76
 
77
+ # TODO use for many of the other macros
78
+ def it_should_have_tag_with(type, attribute_value_hash)
79
+ attribute_value_hash.each do |attribute, value|
80
+ it "should have a #{type} box with #{attribute} '#{value}'" do
81
+ output_buffer.should have_tag("form li #{type}[@#{attribute}=\"#{value}\"]")
82
+ end
83
+ end
84
+ end
85
+ def it_should_have_input_with(attribute_value_hash)
86
+ it_should_have_tag_with(:input, attribute_value_hash)
87
+ end
88
+
89
+ def it_should_have_many_tags(type, count)
90
+ it "should have #{count} #{type} tags" do
91
+ output_buffer.should have_tag("form li #{type}", count: count)
92
+ end
93
+ end
94
+
77
95
  def it_should_have_input_with_type(input_type)
78
96
  it "should have a #{input_type} input" do
79
97
  output_buffer.should have_tag("form li input[@type=\"#{input_type}\"]")
@@ -459,7 +477,7 @@ module CustomMacros
459
477
 
460
478
  describe "when the collection objects respond to #{label_method}" do
461
479
  before do
462
- @fred.stub(:respond_to?).and_return { |m| m.to_s == label_method || m.to_s == 'id' }
480
+ @fred.stub(:respond_to?) { |m| m.to_s == label_method || m.to_s == 'id' }
463
481
  [@fred, @bob].each { |a| a.stub(label_method).and_return('The Label Text') }
464
482
 
465
483
  concat(semantic_form_for(@new_post) do |builder|
@@ -0,0 +1,1244 @@
1
+ RSpec.shared_context 'form builder' do
2
+ include FormtasticSpecHelper
3
+
4
+ before do
5
+ @output_buffer = ''
6
+ mock_everything
7
+ end
8
+
9
+ after do
10
+ ::I18n.backend.reload!
11
+ end
12
+ end
13
+
14
+ # TODO: move this back to spec/helpers/action_helper_spec.rb in Formtastic 4.0
15
+ RSpec.shared_examples 'Action Helper' do
16
+ include_context 'form builder'
17
+
18
+ describe 'arguments and options' do
19
+
20
+ it 'should require the first argument (the action method)' do
21
+ lambda {
22
+ concat(semantic_form_for(@new_post) do |builder|
23
+ concat(builder.action()) # no args passed in at all
24
+ end)
25
+ }.should raise_error(ArgumentError)
26
+ end
27
+
28
+ describe ':as option' do
29
+
30
+ describe 'when not provided' do
31
+
32
+ it 'should default to a commit for commit' do
33
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
34
+ concat(builder.action(:submit))
35
+ end)
36
+ output_buffer.should have_tag('form li.action.input_action', :count => 1)
37
+ end
38
+
39
+ it 'should default to a button for reset' do
40
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
41
+ concat(builder.action(:reset))
42
+ end)
43
+ output_buffer.should have_tag('form li.action.input_action', :count => 1)
44
+ end
45
+
46
+ it 'should default to a link for cancel' do
47
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
48
+ concat(builder.action(:cancel))
49
+ end)
50
+ output_buffer.should have_tag('form li.action.link_action', :count => 1)
51
+ end
52
+ end
53
+
54
+ it 'should call the corresponding action class with .to_html' do
55
+ [:input, :button, :link].each do |action_style|
56
+ semantic_form_for(:project, :url => "http://test.host") do |builder|
57
+ action_instance = double('Action instance')
58
+ action_class = "#{action_style.to_s}_action".classify
59
+ action_constant = "Formtastic::Actions::#{action_class}".constantize
60
+
61
+ action_constant.should_receive(:new).and_return(action_instance)
62
+ action_instance.should_receive(:to_html).and_return("some HTML")
63
+
64
+ concat(builder.action(:submit, :as => action_style))
65
+ end
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ #describe ':label option' do
72
+ #
73
+ # describe 'when provided' do
74
+ # it 'should be passed down to the label tag' do
75
+ # concat(semantic_form_for(@new_post) do |builder|
76
+ # concat(builder.input(:title, :label => "Kustom"))
77
+ # end)
78
+ # output_buffer.should have_tag("form li label", /Kustom/)
79
+ # end
80
+ #
81
+ # it 'should not generate a label if false' do
82
+ # concat(semantic_form_for(@new_post) do |builder|
83
+ # concat(builder.input(:title, :label => false))
84
+ # end)
85
+ # output_buffer.should_not have_tag("form li label")
86
+ # end
87
+ #
88
+ # it 'should be dupped if frozen' do
89
+ # concat(semantic_form_for(@new_post) do |builder|
90
+ # concat(builder.input(:title, :label => "Kustom".freeze))
91
+ # end)
92
+ # output_buffer.should have_tag("form li label", /Kustom/)
93
+ # end
94
+ # end
95
+ #
96
+ # describe 'when not provided' do
97
+ # describe 'when localized label is provided' do
98
+ # describe 'and object is given' do
99
+ # describe 'and label_str_method not :humanize' do
100
+ # it 'should render a label with localized text and not apply the label_str_method' do
101
+ # with_config :label_str_method, :reverse do
102
+ # @localized_label_text = 'Localized title'
103
+ # @new_post.stub(:meta_description)
104
+ # ::I18n.backend.store_translations :en,
105
+ # :formtastic => {
106
+ # :labels => {
107
+ # :meta_description => @localized_label_text
108
+ # }
109
+ # }
110
+ #
111
+ # concat(semantic_form_for(@new_post) do |builder|
112
+ # concat(builder.input(:meta_description))
113
+ # end)
114
+ # output_buffer.should have_tag('form li label', /Localized title/)
115
+ # end
116
+ # end
117
+ # end
118
+ # end
119
+ # end
120
+ #
121
+ # describe 'when localized label is NOT provided' do
122
+ # describe 'and object is not given' do
123
+ # it 'should default the humanized method name, passing it down to the label tag' do
124
+ # ::I18n.backend.store_translations :en, :formtastic => {}
125
+ # with_config :label_str_method, :humanize do
126
+ # concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
127
+ # concat(builder.input(:meta_description))
128
+ # end)
129
+ # output_buffer.should have_tag("form li label", /#{'meta_description'.humanize}/)
130
+ # end
131
+ # end
132
+ # end
133
+ #
134
+ # describe 'and object is given' do
135
+ # it 'should delegate the label logic to class human attribute name and pass it down to the label tag' do
136
+ # @new_post.stub(:meta_description) # a two word method name
137
+ # @new_post.class.should_receive(:human_attribute_name).with('meta_description').and_return('meta_description'.humanize)
138
+ #
139
+ # concat(semantic_form_for(@new_post) do |builder|
140
+ # concat(builder.input(:meta_description))
141
+ # end)
142
+ # output_buffer.should have_tag("form li label", /#{'meta_description'.humanize}/)
143
+ # end
144
+ # end
145
+ #
146
+ # describe 'and object is given with label_str_method set to :capitalize' do
147
+ # it 'should capitalize method name, passing it down to the label tag' do
148
+ # with_config :label_str_method, :capitalize do
149
+ # @new_post.stub(:meta_description)
150
+ #
151
+ # concat(semantic_form_for(@new_post) do |builder|
152
+ # concat(builder.input(:meta_description))
153
+ # end)
154
+ # output_buffer.should have_tag("form li label", /#{'meta_description'.capitalize}/)
155
+ # end
156
+ # end
157
+ # end
158
+ # end
159
+ #
160
+ # describe 'when localized label is provided' do
161
+ # before do
162
+ # @localized_label_text = 'Localized title'
163
+ # @default_localized_label_text = 'Default localized title'
164
+ # ::I18n.backend.store_translations :en,
165
+ # :formtastic => {
166
+ # :labels => {
167
+ # :title => @default_localized_label_text,
168
+ # :published => @default_localized_label_text,
169
+ # :post => {
170
+ # :title => @localized_label_text,
171
+ # :published => @default_localized_label_text
172
+ # }
173
+ # }
174
+ # }
175
+ # end
176
+ #
177
+ # it 'should render a label with localized label (I18n)' do
178
+ # with_config :i18n_lookups_by_default, false do
179
+ # concat(semantic_form_for(@new_post) do |builder|
180
+ # concat(builder.input(:title, :label => true))
181
+ # concat(builder.input(:published, :as => :boolean, :label => true))
182
+ # end)
183
+ # output_buffer.should have_tag('form li label', Regexp.new('^' + @localized_label_text))
184
+ # end
185
+ # end
186
+ #
187
+ # it 'should render a hint paragraph containing an optional localized label (I18n) if first is not set' do
188
+ # with_config :i18n_lookups_by_default, false do
189
+ # ::I18n.backend.store_translations :en,
190
+ # :formtastic => {
191
+ # :labels => {
192
+ # :post => {
193
+ # :title => nil,
194
+ # :published => nil
195
+ # }
196
+ # }
197
+ # }
198
+ # concat(semantic_form_for(@new_post) do |builder|
199
+ # concat(builder.input(:title, :label => true))
200
+ # concat(builder.input(:published, :as => :boolean, :label => true))
201
+ # end)
202
+ # output_buffer.should have_tag('form li label', Regexp.new('^' + @default_localized_label_text))
203
+ # end
204
+ # end
205
+ # end
206
+ # end
207
+ #
208
+ #end
209
+ #
210
+ describe ':wrapper_html option' do
211
+
212
+ describe 'when provided' do
213
+ it 'should be passed down to the li tag' do
214
+ concat(semantic_form_for(@new_post) do |builder|
215
+ concat(builder.action(:submit, :wrapper_html => {:id => :another_id}))
216
+ end)
217
+ output_buffer.should have_tag("form li#another_id")
218
+ end
219
+
220
+ it 'should append given classes to li default classes' do
221
+ concat(semantic_form_for(@new_post) do |builder|
222
+ concat(builder.action(:submit, :wrapper_html => {:class => :another_class}))
223
+ end)
224
+ output_buffer.should have_tag("form li.action")
225
+ output_buffer.should have_tag("form li.input_action")
226
+ output_buffer.should have_tag("form li.another_class")
227
+ end
228
+
229
+ it 'should allow classes to be an array' do
230
+ concat(semantic_form_for(@new_post) do |builder|
231
+ concat(builder.action(:submit, :wrapper_html => {:class => [ :my_class, :another_class ]}))
232
+ end)
233
+ output_buffer.should have_tag("form li.action")
234
+ output_buffer.should have_tag("form li.input_action")
235
+ output_buffer.should have_tag("form li.my_class")
236
+ output_buffer.should have_tag("form li.another_class")
237
+ end
238
+ end
239
+
240
+ describe 'when not provided' do
241
+ it 'should use default id and class' do
242
+ concat(semantic_form_for(@new_post) do |builder|
243
+ concat(builder.action(:submit))
244
+ end)
245
+ output_buffer.should have_tag("form li#post_submit_action")
246
+ output_buffer.should have_tag("form li.action")
247
+ output_buffer.should have_tag("form li.input_action")
248
+ end
249
+ end
250
+
251
+ end
252
+
253
+ end
254
+
255
+ describe 'instantiating an action class' do
256
+ context 'when a class does not exist' do
257
+ it "should raise an error" do
258
+ lambda {
259
+ concat(semantic_form_for(@new_post) do |builder|
260
+ builder.action(:submit, :as => :non_existant)
261
+ end)
262
+ }.should raise_error(Formtastic::UnknownActionError)
263
+ end
264
+ end
265
+
266
+ context 'when a customized top-level class does not exist' do
267
+ it 'should instantiate the Formtastic action' do
268
+ action = double('action', :to_html => 'some HTML')
269
+ Formtastic::Actions::ButtonAction.should_receive(:new).and_return(action)
270
+ concat(semantic_form_for(@new_post) do |builder|
271
+ builder.action(:commit, :as => :button)
272
+ end)
273
+ end
274
+ end
275
+
276
+ describe 'when a top-level (custom) action class exists' do
277
+ it "should instantiate the top-level action instead of the Formtastic one" do
278
+ class ::ButtonAction < Formtastic::Actions::ButtonAction
279
+ end
280
+
281
+ action = double('action', :to_html => 'some HTML')
282
+ Formtastic::Actions::ButtonAction.should_not_receive(:new)
283
+ ::ButtonAction.should_receive(:new).and_return(action)
284
+
285
+ concat(semantic_form_for(@new_post) do |builder|
286
+ builder.action(:commit, :as => :button)
287
+ end)
288
+ end
289
+ end
290
+
291
+ describe 'support for :as on each action' do
292
+
293
+ it "should raise an error when the action does not support the :as" do
294
+ lambda {
295
+ concat(semantic_form_for(@new_post) do |builder|
296
+ concat(builder.action(:submit, :as => :link))
297
+ end)
298
+ }.should raise_error(Formtastic::UnsupportedMethodForAction)
299
+
300
+ lambda {
301
+ concat(semantic_form_for(@new_post) do |builder|
302
+ concat(builder.action(:cancel, :as => :input))
303
+ end)
304
+ }.should raise_error(Formtastic::UnsupportedMethodForAction)
305
+
306
+ lambda {
307
+ concat(semantic_form_for(@new_post) do |builder|
308
+ concat(builder.action(:cancel, :as => :button))
309
+ end)
310
+ }.should raise_error(Formtastic::UnsupportedMethodForAction)
311
+ end
312
+
313
+ it "should not raise an error when the action does not support the :as" do
314
+ lambda {
315
+ concat(semantic_form_for(@new_post) do |builder|
316
+ concat(builder.action(:cancel, :as => :link))
317
+ end)
318
+ }.should_not raise_error
319
+
320
+ lambda {
321
+ concat(semantic_form_for(@new_post) do |builder|
322
+ concat(builder.action(:submit, :as => :input))
323
+ end)
324
+ }.should_not raise_error
325
+
326
+ lambda {
327
+ concat(semantic_form_for(@new_post) do |builder|
328
+ concat(builder.action(:submit, :as => :button))
329
+ end)
330
+ }.should_not raise_error
331
+
332
+ lambda {
333
+ concat(semantic_form_for(@new_post) do |builder|
334
+ concat(builder.action(:reset, :as => :input))
335
+ end)
336
+ }.should_not raise_error
337
+
338
+ lambda {
339
+ concat(semantic_form_for(@new_post) do |builder|
340
+ concat(builder.action(:reset, :as => :button))
341
+ end)
342
+ }.should_not raise_error
343
+ end
344
+
345
+ end
346
+
347
+ end
348
+
349
+ end
350
+
351
+ # TODO: move this back to spec/helpers/input_helper_spec.rb in Formtastic 4.0
352
+ RSpec.shared_examples 'Input Helper' do
353
+ include_context 'form builder'
354
+
355
+ before do
356
+ @errors = double('errors')
357
+ @errors.stub(:[]).and_return([])
358
+ @new_post.stub(:errors).and_return(@errors)
359
+ end
360
+
361
+ describe 'arguments and options' do
362
+
363
+ it 'should require the first argument (the method on form\'s object)' do
364
+ lambda {
365
+ concat(semantic_form_for(@new_post) do |builder|
366
+ concat(builder.input()) # no args passed in at all
367
+ end)
368
+ }.should raise_error(ArgumentError)
369
+ end
370
+
371
+ describe ':required option' do
372
+
373
+ describe 'when true' do
374
+
375
+ it 'should set a "required" class' do
376
+ with_config :required_string, " required yo!" do
377
+ concat(semantic_form_for(@new_post) do |builder|
378
+ concat(builder.input(:title, :required => true))
379
+ end)
380
+ output_buffer.should_not have_tag('form li.optional')
381
+ output_buffer.should have_tag('form li.required')
382
+ end
383
+ end
384
+
385
+ it 'should append the "required" string to the label' do
386
+ with_config :required_string, " required yo!" do
387
+ concat(semantic_form_for(@new_post) do |builder|
388
+ concat(builder.input(:title, :required => true))
389
+ end)
390
+ output_buffer.should have_tag('form li.required label', /required yo/)
391
+ end
392
+ end
393
+ end
394
+
395
+ describe 'when false' do
396
+
397
+ before do
398
+ @string = Formtastic::FormBuilder.optional_string = " optional yo!" # ensure there's something in the string
399
+ @new_post.class.should_not_receive(:reflect_on_all_validations)
400
+ end
401
+
402
+ after do
403
+ Formtastic::FormBuilder.optional_string = ''
404
+ end
405
+
406
+ it 'should set an "optional" class' do
407
+ concat(semantic_form_for(@new_post) do |builder|
408
+ concat(builder.input(:title, :required => false))
409
+ end)
410
+ output_buffer.should_not have_tag('form li.required')
411
+ output_buffer.should have_tag('form li.optional')
412
+ end
413
+
414
+ it 'should set and "optional" class also when there is presence validator' do
415
+ @new_post.class.should_receive(:validators_on).with(:title).at_least(:once).and_return([
416
+ active_model_presence_validator([:title])
417
+ ])
418
+ concat(semantic_form_for(@new_post) do |builder|
419
+ concat(builder.input(:title, :required => false))
420
+ end)
421
+ output_buffer.should_not have_tag('form li.required')
422
+ output_buffer.should have_tag('form li.optional')
423
+ end
424
+
425
+ it 'should append the "optional" string to the label' do
426
+ concat(semantic_form_for(@new_post) do |builder|
427
+ concat(builder.input(:title, :required => false))
428
+ end)
429
+ output_buffer.should have_tag('form li.optional label', /#{@string}$/)
430
+ end
431
+
432
+ end
433
+
434
+ describe 'when not provided' do
435
+
436
+ describe 'and an object was not given' do
437
+
438
+ it 'should use the default value' do
439
+ Formtastic::FormBuilder.all_fields_required_by_default.should == true
440
+ Formtastic::FormBuilder.all_fields_required_by_default = false
441
+
442
+ concat(semantic_form_for(:project, :url => 'http://test.host/') do |builder|
443
+ concat(builder.input(:title))
444
+ end)
445
+ output_buffer.should_not have_tag('form li.required')
446
+ output_buffer.should have_tag('form li.optional')
447
+
448
+ Formtastic::FormBuilder.all_fields_required_by_default = true
449
+ end
450
+
451
+ end
452
+
453
+ describe 'and an object with :validators_on was given (ActiveModel, Active Resource)' do
454
+ before do
455
+ @new_post.stub(:class).and_return(::PostModel)
456
+ end
457
+
458
+ after do
459
+ @new_post.stub(:class).and_return(::Post)
460
+ end
461
+ describe 'and validates_presence_of was called for the method' do
462
+ it 'should be required' do
463
+
464
+ @new_post.class.should_receive(:validators_on).with(:title).at_least(:once).and_return([
465
+ active_model_presence_validator([:title])
466
+ ])
467
+
468
+ @new_post.class.should_receive(:validators_on).with(:body).at_least(:once).and_return([
469
+ active_model_presence_validator([:body], {:if => true})
470
+ ])
471
+
472
+ concat(semantic_form_for(@new_post) do |builder|
473
+ concat(builder.input(:title))
474
+ concat(builder.input(:body))
475
+ end)
476
+ output_buffer.should have_tag('form li.required')
477
+ output_buffer.should_not have_tag('form li.optional')
478
+ end
479
+
480
+ it 'should be required when there is :on => :create option on create' do
481
+ with_config :required_string, " required yo!" do
482
+ @new_post.class.should_receive(:validators_on).with(:title).at_least(:once).and_return([
483
+ active_model_presence_validator([:title], {:on => :create})
484
+ ])
485
+ concat(semantic_form_for(@new_post) do |builder|
486
+ concat(builder.input(:title))
487
+ end)
488
+ output_buffer.should have_tag('form li.required')
489
+ output_buffer.should_not have_tag('form li.optional')
490
+ end
491
+ end
492
+
493
+ it 'should be required when there is :on => :save option on create' do
494
+ with_config :required_string, " required yo!" do
495
+ @new_post.class.should_receive(:validators_on).with(:title).at_least(:once).and_return([
496
+ active_model_presence_validator([:title], {:on => :save})
497
+ ])
498
+ concat(semantic_form_for(@new_post) do |builder|
499
+ concat(builder.input(:title))
500
+ end)
501
+ output_buffer.should have_tag('form li.required')
502
+ output_buffer.should_not have_tag('form li.optional')
503
+ end
504
+ end
505
+
506
+ it 'should be required when there is :on => :save option on update' do
507
+ with_config :required_string, " required yo!" do
508
+ @fred.class.should_receive(:validators_on).with(:login).at_least(:once).and_return([
509
+ active_model_presence_validator([:login], {:on => :save})
510
+ ])
511
+ concat(semantic_form_for(@fred) do |builder|
512
+ concat(builder.input(:login))
513
+ end)
514
+ output_buffer.should have_tag('form li.required')
515
+ output_buffer.should_not have_tag('form li.optional')
516
+ end
517
+ end
518
+
519
+ it 'should not be required when there is :on => :create option on update' do
520
+ @fred.class.should_receive(:validators_on).with(:login).at_least(:once).and_return([
521
+ active_model_presence_validator([:login], {:on => :create})
522
+ ])
523
+ concat(semantic_form_for(@fred) do |builder|
524
+ concat(builder.input(:login))
525
+ end)
526
+ output_buffer.should_not have_tag('form li.required')
527
+ output_buffer.should have_tag('form li.optional')
528
+ end
529
+
530
+ it 'should not be required when there is :on => :update option on create' do
531
+ @new_post.class.should_receive(:validators_on).with(:title).at_least(:once).and_return([
532
+ active_model_presence_validator([:title], {:on => :update})
533
+ ])
534
+ concat(semantic_form_for(@new_post) do |builder|
535
+ concat(builder.input(:title))
536
+ end)
537
+ output_buffer.should_not have_tag('form li.required')
538
+ output_buffer.should have_tag('form li.optional')
539
+ end
540
+
541
+ it 'should be not be required if the optional :if condition is not satisifed' do
542
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => false })
543
+ end
544
+
545
+ it 'should not be required if the optional :if proc evaluates to false' do
546
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => proc { |record| false } })
547
+ end
548
+
549
+ it 'should be required if the optional :if proc evaluates to true' do
550
+ presence_should_be_required(:required => true, :tag => :body, :options => { :if => proc { |record| true } })
551
+ end
552
+
553
+ it 'should not be required if the optional :unless proc evaluates to true' do
554
+ presence_should_be_required(:required => false, :tag => :body, :options => { :unless => proc { |record| true } })
555
+ end
556
+
557
+ it 'should be required if the optional :unless proc evaluates to false' do
558
+ presence_should_be_required(:required => true, :tag => :body, :options => { :unless => proc { |record| false } })
559
+ end
560
+
561
+ it 'should be required if the optional :if with a method string evaluates to true' do
562
+ @new_post.should_receive(:required_condition).and_return(true)
563
+ presence_should_be_required(:required => true, :tag => :body, :options => { :if => :required_condition })
564
+ end
565
+
566
+ it 'should be required if the optional :if with a method string evaluates to false' do
567
+ @new_post.should_receive(:required_condition).and_return(false)
568
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => :required_condition })
569
+ end
570
+
571
+ it 'should be required if the optional :unless with a method string evaluates to false' do
572
+ @new_post.should_receive(:required_condition).and_return(false)
573
+ presence_should_be_required(:required => true, :tag => :body, :options => { :unless => :required_condition })
574
+ end
575
+
576
+ it 'should not be required if the optional :unless with a method string evaluates to true' do
577
+ @new_post.should_receive(:required_condition).and_return(true)
578
+ presence_should_be_required(:required => false, :tag => :body, :options => { :unless => :required_condition })
579
+ end
580
+ end
581
+
582
+ describe 'and validates_inclusion_of was called for the method' do
583
+ it 'should be required' do
584
+ @new_post.class.should_receive(:validators_on).with(:published).at_least(:once).and_return([
585
+ active_model_inclusion_validator([:published], {:in => [false, true]})
586
+ ])
587
+ should_be_required(:tag => :published, :required => true)
588
+ end
589
+
590
+ it 'should not be required if allow_blank is true' do
591
+ @new_post.class.should_receive(:validators_on).with(:published).at_least(:once).and_return([
592
+ active_model_inclusion_validator([:published], {:in => [false, true], :allow_blank => true})
593
+ ])
594
+ should_be_required(:tag => :published, :required => false)
595
+ end
596
+ end
597
+
598
+ describe 'and validates_length_of was called for the method' do
599
+ it 'should be required if minimum is set' do
600
+ length_should_be_required(:tag => :title, :required => true, :options => {:minimum => 1})
601
+ end
602
+
603
+ it 'should be required if :within is set' do
604
+ length_should_be_required(:tag => :title, :required => true, :options => {:within => 1..5})
605
+ end
606
+
607
+ it 'should not be required if :within allows zero length' do
608
+ length_should_be_required(:tag => :title, :required => false, :options => {:within => 0..5})
609
+ end
610
+
611
+ it 'should not be required if only :minimum is zero' do
612
+ length_should_be_required(:tag => :title, :required => false, :options => {:minimum => 0})
613
+ end
614
+
615
+ it 'should not be required if only :minimum is not set' do
616
+ length_should_be_required(:tag => :title, :required => false, :options => {:maximum => 5})
617
+ end
618
+
619
+ it 'should not be required if allow_blank is true' do
620
+ length_should_be_required(:tag => :published, :required => false, :options => {:allow_blank => true})
621
+ end
622
+ end
623
+
624
+ def add_presence_validator(options)
625
+ @new_post.class.stub(:validators_on).with(options[:tag]).and_return([
626
+ active_model_presence_validator([options[:tag]], options[:options])
627
+ ])
628
+ end
629
+
630
+ def add_length_validator(options)
631
+ @new_post.class.should_receive(:validators_on).with(options[:tag]).at_least(:once) {[
632
+ active_model_length_validator([options[:tag]], options[:options])
633
+ ]}
634
+ end
635
+
636
+ # TODO make a matcher for this?
637
+ def should_be_required(options)
638
+ concat(semantic_form_for(@new_post) do |builder|
639
+ concat(builder.input(options[:tag]))
640
+ end)
641
+
642
+ if options[:required]
643
+ output_buffer.should_not have_tag('form li.optional')
644
+ output_buffer.should have_tag('form li.required')
645
+ else
646
+ output_buffer.should have_tag('form li.optional')
647
+ output_buffer.should_not have_tag('form li.required')
648
+ end
649
+ end
650
+
651
+ def presence_should_be_required(options)
652
+ add_presence_validator(options)
653
+ should_be_required(options)
654
+ end
655
+
656
+ def length_should_be_required(options)
657
+ add_length_validator(options)
658
+ should_be_required(options)
659
+ end
660
+
661
+ # TODO JF reversed this during refactor, need to make sure
662
+ describe 'and there are no requirement validations on the method' do
663
+ before do
664
+ @new_post.class.should_receive(:validators_on).with(:title).and_return([])
665
+ end
666
+
667
+ it 'should not be required' do
668
+ concat(semantic_form_for(@new_post) do |builder|
669
+ concat(builder.input(:title))
670
+ end)
671
+ output_buffer.should_not have_tag('form li.required')
672
+ output_buffer.should have_tag('form li.optional')
673
+ end
674
+ end
675
+
676
+ end
677
+
678
+ describe 'and an object without :validators_on' do
679
+
680
+ it 'should use the default value' do
681
+ Formtastic::FormBuilder.all_fields_required_by_default.should == true
682
+ Formtastic::FormBuilder.all_fields_required_by_default = false
683
+
684
+ concat(semantic_form_for(@new_post) do |builder|
685
+ concat(builder.input(:title))
686
+ end)
687
+ output_buffer.should_not have_tag('form li.required')
688
+ output_buffer.should have_tag('form li.optional')
689
+
690
+ Formtastic::FormBuilder.all_fields_required_by_default = true
691
+ end
692
+
693
+ end
694
+
695
+ end
696
+
697
+ end
698
+
699
+ describe ':as option' do
700
+
701
+ describe 'when not provided' do
702
+
703
+ it 'should default to a string for forms without objects unless column is password' do
704
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
705
+ concat(builder.input(:anything))
706
+ end)
707
+ output_buffer.should have_tag('form li.string')
708
+ end
709
+
710
+ it 'should default to password for forms without objects if column is password' do
711
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
712
+ concat(builder.input(:password))
713
+ concat(builder.input(:password_confirmation))
714
+ concat(builder.input(:confirm_password))
715
+ end)
716
+ output_buffer.should have_tag('form li.password', :count => 3)
717
+ end
718
+
719
+ it 'should default to a string for methods on objects that don\'t respond to "column_for_attribute"' do
720
+ @new_post.stub(:method_without_a_database_column)
721
+ @new_post.stub(:column_for_attribute).and_return(nil)
722
+ default_input_type(nil, :method_without_a_database_column).should == :string
723
+ end
724
+
725
+ it 'should default to :password for methods that don\'t have a column in the database but "password" is in the method name' do
726
+ @new_post.stub(:password_method_without_a_database_column)
727
+ @new_post.stub(:column_for_attribute).and_return(nil)
728
+ default_input_type(nil, :password_method_without_a_database_column).should == :password
729
+ end
730
+
731
+ 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
732
+ @new_post.stub(:password_method_without_a_database_column)
733
+ @new_post.stub(:column_for_attribute).and_return(nil)
734
+ default_input_type(nil, :password_method_without_a_database_column).should == :password
735
+ end
736
+
737
+ it 'should default to :number for "integer" column with name ending in "_id"' do
738
+ @new_post.stub(:aws_instance_id)
739
+ @new_post.stub(:column_for_attribute).with(:aws_instance_id).and_return(double('column', :type => :integer))
740
+ default_input_type(:integer, :aws_instance_id).should == :number
741
+ end
742
+
743
+ it 'should default to :select for associations' do
744
+ @new_post.class.stub(:reflect_on_association).with(:user_id).and_return(double('ActiveRecord::Reflection::AssociationReflection'))
745
+ @new_post.class.stub(:reflect_on_association).with(:section_id).and_return(double('ActiveRecord::Reflection::AssociationReflection'))
746
+ default_input_type(:integer, :user_id).should == :select
747
+ default_input_type(:integer, :section_id).should == :select
748
+ end
749
+
750
+ it 'should default to :password for :string column types with "password" in the method name' do
751
+ default_input_type(:string, :password).should == :password
752
+ default_input_type(:string, :hashed_password).should == :password
753
+ default_input_type(:string, :password_hash).should == :password
754
+ end
755
+
756
+ it 'should default to :text for :text column types' do
757
+ default_input_type(:text).should == :text
758
+ end
759
+
760
+ it 'should default to :date_select for :date column types' do
761
+ default_input_type(:date).should == :date_select
762
+ end
763
+
764
+ it 'should default to :datetime_select for :datetime and :timestamp column types' do
765
+ default_input_type(:datetime).should == :datetime_select
766
+ default_input_type(:timestamp).should == :datetime_select
767
+ end
768
+
769
+ it 'should default to :time_select for :time column types' do
770
+ default_input_type(:time).should == :time_select
771
+ end
772
+
773
+ it 'should default to :boolean for :boolean column types' do
774
+ default_input_type(:boolean).should == :boolean
775
+ end
776
+
777
+ it 'should default to :string for :string column types' do
778
+ default_input_type(:string).should == :string
779
+ end
780
+
781
+ it 'should default to :number for :integer, :float and :decimal column types' do
782
+ default_input_type(:integer).should == :number
783
+ default_input_type(:float).should == :number
784
+ default_input_type(:decimal).should == :number
785
+ end
786
+
787
+ it 'should default to :country for :string columns named country' do
788
+ default_input_type(:string, :country).should == :country
789
+ end
790
+
791
+ it 'should default to :email for :string columns matching email' do
792
+ default_input_type(:string, :email).should == :email
793
+ default_input_type(:string, :customer_email).should == :email
794
+ default_input_type(:string, :email_work).should == :email
795
+ end
796
+
797
+ it 'should default to :url for :string columns named url or website' do
798
+ default_input_type(:string, :url).should == :url
799
+ default_input_type(:string, :website).should == :url
800
+ default_input_type(:string, :my_url).should == :url
801
+ default_input_type(:string, :hurl).should_not == :url
802
+ end
803
+
804
+ it 'should default to :phone for :string columns named phone or fax' do
805
+ default_input_type(:string, :phone).should == :phone
806
+ default_input_type(:string, :fax).should == :phone
807
+ end
808
+
809
+ it 'should default to :search for :string columns named search' do
810
+ default_input_type(:string, :search).should == :search
811
+ end
812
+
813
+ it 'should default to :color for :string columns matching color' do
814
+ default_input_type(:string, :color).should == :color
815
+ default_input_type(:string, :user_color).should == :color
816
+ default_input_type(:string, :color_for_image).should == :color
817
+ end
818
+
819
+ describe 'defaulting to file column' do
820
+ Formtastic::FormBuilder.file_methods.each do |method|
821
+ it "should default to :file for attributes that respond to ##{method}" do
822
+ column = double('column')
823
+
824
+ Formtastic::FormBuilder.file_methods.each do |test|
825
+ ### TODO: Check if this is ok
826
+ column.stub(method).with(test).and_return(method == test)
827
+ end
828
+
829
+ @new_post.should_receive(method).and_return(column)
830
+
831
+ semantic_form_for(@new_post) do |builder|
832
+ builder.send(:default_input_type, method).should == :file
833
+ end
834
+ end
835
+ end
836
+
837
+ end
838
+ end
839
+
840
+ it 'should call the corresponding input class with .to_html' do
841
+ [:select, :time_zone, :radio, :date_select, :datetime_select, :time_select, :boolean, :check_boxes, :hidden, :string, :password, :number, :text, :file].each do |input_style|
842
+ @new_post.stub(:generic_column_name)
843
+ @new_post.stub(:column_for_attribute).and_return(double('column', :type => :string, :limit => 255))
844
+ semantic_form_for(@new_post) do |builder|
845
+ input_instance = double('Input instance')
846
+ input_class = "#{input_style.to_s}_input".classify
847
+ input_constant = "Formtastic::Inputs::#{input_class}".constantize
848
+
849
+ input_constant.should_receive(:new).and_return(input_instance)
850
+ input_instance.should_receive(:to_html).and_return("some HTML")
851
+
852
+ concat(builder.input(:generic_column_name, :as => input_style))
853
+ end
854
+ end
855
+ end
856
+
857
+ end
858
+
859
+ describe ':label option' do
860
+
861
+ describe 'when provided' do
862
+ it 'should be passed down to the label tag' do
863
+ concat(semantic_form_for(@new_post) do |builder|
864
+ concat(builder.input(:title, :label => "Kustom"))
865
+ end)
866
+ output_buffer.should have_tag("form li label", /Kustom/)
867
+ end
868
+
869
+ it 'should not generate a label if false' do
870
+ concat(semantic_form_for(@new_post) do |builder|
871
+ concat(builder.input(:title, :label => false))
872
+ end)
873
+ output_buffer.should_not have_tag("form li label")
874
+ end
875
+
876
+ it 'should be dupped if frozen' do
877
+ concat(semantic_form_for(@new_post) do |builder|
878
+ concat(builder.input(:title, :label => "Kustom".freeze))
879
+ end)
880
+ output_buffer.should have_tag("form li label", /Kustom/)
881
+ end
882
+ end
883
+
884
+ describe 'when not provided' do
885
+ describe 'when localized label is provided' do
886
+ describe 'and object is given' do
887
+ describe 'and label_str_method not :humanize' do
888
+ it 'should render a label with localized text and not apply the label_str_method' do
889
+ with_config :label_str_method, :reverse do
890
+ @localized_label_text = 'Localized title'
891
+ @new_post.stub(:meta_description)
892
+ ::I18n.backend.store_translations :en,
893
+ :formtastic => {
894
+ :labels => {
895
+ :meta_description => @localized_label_text
896
+ }
897
+ }
898
+
899
+ concat(semantic_form_for(@new_post) do |builder|
900
+ concat(builder.input(:meta_description))
901
+ end)
902
+ output_buffer.should have_tag('form li label', /Localized title/)
903
+ end
904
+ end
905
+ end
906
+ end
907
+ end
908
+
909
+ describe 'when localized label is NOT provided' do
910
+ describe 'and object is not given' do
911
+ it 'should default the humanized method name, passing it down to the label tag' do
912
+ ::I18n.backend.store_translations :en, :formtastic => {}
913
+ with_config :label_str_method, :humanize do
914
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
915
+ concat(builder.input(:meta_description))
916
+ end)
917
+ output_buffer.should have_tag("form li label", /#{'meta_description'.humanize}/)
918
+ end
919
+ end
920
+ end
921
+
922
+ describe 'and object is given' do
923
+ it 'should delegate the label logic to class human attribute name and pass it down to the label tag' do
924
+ @new_post.stub(:meta_description) # a two word method name
925
+ @new_post.class.should_receive(:human_attribute_name).with('meta_description').and_return('meta_description'.humanize)
926
+
927
+ concat(semantic_form_for(@new_post) do |builder|
928
+ concat(builder.input(:meta_description))
929
+ end)
930
+ output_buffer.should have_tag("form li label", /#{'meta_description'.humanize}/)
931
+ end
932
+ end
933
+
934
+ describe 'and object is given with label_str_method set to :capitalize' do
935
+ it 'should capitalize method name, passing it down to the label tag' do
936
+ with_config :label_str_method, :capitalize do
937
+ @new_post.stub(:meta_description)
938
+
939
+ concat(semantic_form_for(@new_post) do |builder|
940
+ concat(builder.input(:meta_description))
941
+ end)
942
+ output_buffer.should have_tag("form li label", /#{'meta_description'.capitalize}/)
943
+ end
944
+ end
945
+ end
946
+ end
947
+
948
+ describe 'when localized label is provided' do
949
+ before do
950
+ @localized_label_text = 'Localized title'
951
+ @default_localized_label_text = 'Default localized title'
952
+ ::I18n.backend.store_translations :en,
953
+ :formtastic => {
954
+ :labels => {
955
+ :title => @default_localized_label_text,
956
+ :published => @default_localized_label_text,
957
+ :post => {
958
+ :title => @localized_label_text,
959
+ :published => @default_localized_label_text
960
+ }
961
+ }
962
+ }
963
+ end
964
+
965
+ it 'should render a label with localized label (I18n)' do
966
+ with_config :i18n_lookups_by_default, false do
967
+ concat(semantic_form_for(@new_post) do |builder|
968
+ concat(builder.input(:title, :label => true))
969
+ concat(builder.input(:published, :as => :boolean, :label => true))
970
+ end)
971
+ output_buffer.should have_tag('form li label', Regexp.new('^' + @localized_label_text))
972
+ end
973
+ end
974
+
975
+ it 'should render a hint paragraph containing an optional localized label (I18n) if first is not set' do
976
+ with_config :i18n_lookups_by_default, false do
977
+ ::I18n.backend.store_translations :en,
978
+ :formtastic => {
979
+ :labels => {
980
+ :post => {
981
+ :title => nil,
982
+ :published => nil
983
+ }
984
+ }
985
+ }
986
+ concat(semantic_form_for(@new_post) do |builder|
987
+ concat(builder.input(:title, :label => true))
988
+ concat(builder.input(:published, :as => :boolean, :label => true))
989
+ end)
990
+ output_buffer.should have_tag('form li label', Regexp.new('^' + @default_localized_label_text))
991
+ end
992
+ end
993
+ end
994
+ end
995
+
996
+ end
997
+
998
+ describe ':hint option' do
999
+
1000
+ describe 'when provided' do
1001
+
1002
+ after do
1003
+ Formtastic::FormBuilder.default_hint_class = "inline-hints"
1004
+ end
1005
+
1006
+ it 'should be passed down to the paragraph tag' do
1007
+ hint_text = "this is the title of the post"
1008
+ concat(semantic_form_for(@new_post) do |builder|
1009
+ concat(builder.input(:title, :hint => hint_text))
1010
+ end)
1011
+ output_buffer.should have_tag("form li p.inline-hints", hint_text)
1012
+ end
1013
+
1014
+ it 'should have a custom hint class defaulted for all forms' do
1015
+ hint_text = "this is the title of the post"
1016
+ Formtastic::FormBuilder.default_hint_class = "custom-hint-class"
1017
+ concat(semantic_form_for(@new_post) do |builder|
1018
+ concat(builder.input(:title, :hint => hint_text))
1019
+ end)
1020
+ output_buffer.should have_tag("form li p.custom-hint-class", hint_text)
1021
+ end
1022
+ end
1023
+
1024
+ describe 'when not provided' do
1025
+ describe 'when localized hint (I18n) is provided' do
1026
+ before do
1027
+ @localized_hint_text = "This is the localized hint."
1028
+ @default_localized_hint_text = "This is the default localized hint."
1029
+ ::I18n.backend.store_translations :en,
1030
+ :formtastic => {
1031
+ :hints => {
1032
+ :title => @default_localized_hint_text,
1033
+ }
1034
+ }
1035
+ end
1036
+
1037
+ after do
1038
+ ::I18n.backend.reload!
1039
+ end
1040
+
1041
+ describe 'when provided value (hint value) is set to TRUE' do
1042
+ it 'should render a hint paragraph containing a localized hint (I18n)' do
1043
+ with_config :i18n_lookups_by_default, false do
1044
+ ::I18n.backend.store_translations :en,
1045
+ :formtastic => {
1046
+ :hints => {
1047
+ :post => {
1048
+ :title => @localized_hint_text
1049
+ }
1050
+ }
1051
+ }
1052
+ concat(semantic_form_for(@new_post) do |builder|
1053
+ concat(builder.input(:title, :hint => true))
1054
+ end)
1055
+ output_buffer.should have_tag('form li p.inline-hints', @localized_hint_text)
1056
+ end
1057
+ end
1058
+
1059
+ it 'should render a hint paragraph containing an optional localized hint (I18n) if first is not set' do
1060
+ with_config :i18n_lookups_by_default, false do
1061
+ concat(semantic_form_for(@new_post) do |builder|
1062
+ concat(builder.input(:title, :hint => true))
1063
+ end)
1064
+ output_buffer.should have_tag('form li p.inline-hints', @default_localized_hint_text)
1065
+ end
1066
+ end
1067
+ end
1068
+
1069
+ describe 'when provided value (label value) is set to FALSE' do
1070
+ it 'should not render a hint paragraph' do
1071
+ with_config :i18n_lookups_by_default, false do
1072
+ concat(semantic_form_for(@new_post) do |builder|
1073
+ concat(builder.input(:title, :hint => false))
1074
+ end)
1075
+ output_buffer.should_not have_tag('form li p.inline-hints', @localized_hint_text)
1076
+ end
1077
+ end
1078
+ end
1079
+ end
1080
+
1081
+ describe 'when localized hint (I18n) is a model with attribute hints' do
1082
+ it "should see the provided hash as a blank entry" do
1083
+ with_config :i18n_lookups_by_default, false do
1084
+ ::I18n.backend.store_translations :en,
1085
+ :formtastic => {
1086
+ :hints => {
1087
+ :title => { # movie title
1088
+ :summary => @localized_hint_text # summary of movie
1089
+ }
1090
+ }
1091
+ }
1092
+ semantic_form_for(@new_post) do |builder|
1093
+ concat(builder.input(:title, :hint => true))
1094
+ end
1095
+ output_buffer.should_not have_tag('form li p.inline-hints', @localized_hint_text)
1096
+ end
1097
+ end
1098
+ end
1099
+
1100
+ describe 'when localized hint (I18n) is not provided' do
1101
+ it 'should not render a hint paragraph' do
1102
+ with_config :i18n_lookups_by_default, false do
1103
+ concat(semantic_form_for(@new_post) do |builder|
1104
+ concat(builder.input(:title))
1105
+ end)
1106
+ output_buffer.should_not have_tag('form li p.inline-hints')
1107
+ end
1108
+ end
1109
+ end
1110
+ end
1111
+
1112
+ end
1113
+
1114
+ describe ':wrapper_html option' do
1115
+
1116
+ describe 'when provided' do
1117
+ it 'should be passed down to the li tag' do
1118
+ concat(semantic_form_for(@new_post) do |builder|
1119
+ concat(builder.input(:title, :wrapper_html => {:id => :another_id}))
1120
+ end)
1121
+ output_buffer.should have_tag("form li#another_id")
1122
+ end
1123
+
1124
+ it 'should append given classes to li default classes' do
1125
+ concat(semantic_form_for(@new_post) do |builder|
1126
+ concat(builder.input(:title, :wrapper_html => {:class => :another_class}, :required => true))
1127
+ end)
1128
+ output_buffer.should have_tag("form li.string")
1129
+ output_buffer.should have_tag("form li.required")
1130
+ output_buffer.should have_tag("form li.another_class")
1131
+ end
1132
+
1133
+ it 'should allow classes to be an array' do
1134
+ concat(semantic_form_for(@new_post) do |builder|
1135
+ concat(builder.input(:title, :wrapper_html => {:class => [ :my_class, :another_class ]}))
1136
+ end)
1137
+ output_buffer.should have_tag("form li.string")
1138
+ output_buffer.should have_tag("form li.my_class")
1139
+ output_buffer.should have_tag("form li.another_class")
1140
+ end
1141
+
1142
+ describe 'when nil' do
1143
+ it 'should not put an id attribute on the div tag' do
1144
+ concat(semantic_form_for(@new_post) do |builder|
1145
+ concat(builder.input(:title, :wrapper_html => {:id => nil}))
1146
+ end)
1147
+ output_buffer.should have_tag('form li:not([id])')
1148
+ end
1149
+ end
1150
+ end
1151
+
1152
+ describe 'when not provided' do
1153
+ it 'should use default id and class' do
1154
+ concat(semantic_form_for(@new_post) do |builder|
1155
+ concat(builder.input(:title))
1156
+ end)
1157
+ output_buffer.should have_tag("form li#post_title_input")
1158
+ output_buffer.should have_tag("form li.string")
1159
+ end
1160
+ end
1161
+
1162
+ end
1163
+
1164
+ describe ':collection option' do
1165
+
1166
+ it "should be required on polymorphic associations" do
1167
+ @new_post.stub(:commentable)
1168
+ @new_post.class.stub(:reflections).and_return({
1169
+ :commentable => double('macro_reflection', :options => { :polymorphic => true }, :macro => :belongs_to)
1170
+ })
1171
+ @new_post.stub(:column_for_attribute).with(:commentable).and_return(
1172
+ double('column', :type => :integer)
1173
+ )
1174
+ @new_post.class.stub(:reflect_on_association).with(:commentable).and_return(
1175
+ double('reflection', :macro => :belongs_to, :options => { :polymorphic => true })
1176
+ )
1177
+ expect {
1178
+ concat(semantic_form_for(@new_post) do |builder|
1179
+ concat(builder.inputs do
1180
+ concat(builder.input :commentable)
1181
+ end)
1182
+ end)
1183
+ }.to raise_error(Formtastic::PolymorphicInputWithoutCollectionError)
1184
+ end
1185
+
1186
+ end
1187
+
1188
+ end
1189
+
1190
+ describe 'options re-use' do
1191
+
1192
+ it 'should retain :as option when re-using the same options hash' do
1193
+ my_options = { :as => :string }
1194
+ output = ''
1195
+
1196
+ concat(semantic_form_for(@new_post) do |builder|
1197
+ concat(builder.input(:title, my_options))
1198
+ concat(builder.input(:publish_at, my_options))
1199
+ end)
1200
+ output_buffer.should have_tag 'li.string', :count => 2
1201
+ end
1202
+ end
1203
+
1204
+ describe 'instantiating an input class' do
1205
+ context 'when a class does not exist' do
1206
+ it "should raise an error" do
1207
+ lambda {
1208
+ concat(semantic_form_for(@new_post) do |builder|
1209
+ builder.input(:title, :as => :non_existant)
1210
+ end)
1211
+ }.should raise_error(Formtastic::UnknownInputError)
1212
+ end
1213
+ end
1214
+
1215
+ context 'when a customized top-level class does not exist' do
1216
+
1217
+ it 'should instantiate the Formtastic input' do
1218
+ input = double('input', :to_html => 'some HTML')
1219
+ Formtastic::Inputs::StringInput.should_receive(:new).and_return(input)
1220
+ concat(semantic_form_for(@new_post) do |builder|
1221
+ builder.input(:title, :as => :string)
1222
+ end)
1223
+ end
1224
+
1225
+ end
1226
+
1227
+ describe 'when a top-level input class exists' do
1228
+ it "should instantiate the top-level input instead of the Formtastic one" do
1229
+ class ::StringInput < Formtastic::Inputs::StringInput
1230
+ end
1231
+
1232
+ input = double('input', :to_html => 'some HTML')
1233
+ Formtastic::Inputs::StringInput.should_not_receive(:new)
1234
+ ::StringInput.should_receive(:new).and_return(input)
1235
+
1236
+ concat(semantic_form_for(@new_post) do |builder|
1237
+ builder.input(:title, :as => :string)
1238
+ end)
1239
+ end
1240
+ end
1241
+
1242
+
1243
+ end
1244
+ end