formtastic 3.1.5 → 4.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +5 -5
  2. data/.gitattributes +1 -0
  3. data/.gitignore +3 -2
  4. data/.travis.yml +28 -40
  5. data/CHANGELOG.md +49 -0
  6. data/DEPRECATIONS +1 -1
  7. data/Gemfile.lock +104 -0
  8. data/README.md +628 -629
  9. data/Rakefile +20 -1
  10. data/app/assets/stylesheets/formtastic.css +1 -1
  11. data/bin/appraisal +8 -0
  12. data/formtastic.gemspec +8 -14
  13. data/gemfiles/rails_5.2/Gemfile +5 -0
  14. data/gemfiles/rails_6.0/Gemfile +5 -0
  15. data/gemfiles/rails_edge/Gemfile +13 -0
  16. data/lib/formtastic.rb +5 -11
  17. data/lib/formtastic/actions.rb +6 -3
  18. data/lib/formtastic/deprecation.rb +1 -38
  19. data/lib/formtastic/engine.rb +3 -1
  20. data/lib/formtastic/form_builder.rb +8 -24
  21. data/lib/formtastic/helpers/action_helper.rb +1 -48
  22. data/lib/formtastic/helpers/errors_helper.rb +2 -2
  23. data/lib/formtastic/helpers/fieldset_wrapper.rb +7 -3
  24. data/lib/formtastic/helpers/input_helper.rb +18 -76
  25. data/lib/formtastic/helpers/inputs_helper.rb +12 -3
  26. data/lib/formtastic/i18n.rb +1 -1
  27. data/lib/formtastic/inputs.rb +32 -29
  28. data/lib/formtastic/inputs/base/collections.rb +1 -5
  29. data/lib/formtastic/inputs/base/errors.rb +4 -4
  30. data/lib/formtastic/inputs/base/hints.rb +1 -1
  31. data/lib/formtastic/inputs/base/timeish.rb +5 -1
  32. data/lib/formtastic/inputs/base/validations.rb +19 -9
  33. data/lib/formtastic/inputs/check_boxes_input.rb +3 -3
  34. data/lib/formtastic/inputs/color_input.rb +0 -1
  35. data/lib/formtastic/inputs/select_input.rb +1 -1
  36. data/lib/formtastic/localizer.rb +5 -7
  37. data/lib/formtastic/version.rb +1 -1
  38. data/lib/generators/templates/formtastic.rb +4 -6
  39. data/script/integration-template.rb +71 -0
  40. data/script/integration.sh +19 -0
  41. data/spec/action_class_finder_spec.rb +1 -1
  42. data/spec/actions/button_action_spec.rb +8 -8
  43. data/spec/actions/generic_action_spec.rb +60 -60
  44. data/spec/actions/input_action_spec.rb +7 -7
  45. data/spec/actions/link_action_spec.rb +10 -10
  46. data/spec/builder/custom_builder_spec.rb +36 -20
  47. data/spec/builder/error_proc_spec.rb +4 -4
  48. data/spec/builder/semantic_fields_for_spec.rb +27 -27
  49. data/spec/generators/formtastic/form/form_generator_spec.rb +25 -25
  50. data/spec/generators/formtastic/input/input_generator_spec.rb +31 -31
  51. data/spec/generators/formtastic/install/install_generator_spec.rb +9 -9
  52. data/spec/helpers/action_helper_spec.rb +328 -10
  53. data/spec/helpers/actions_helper_spec.rb +17 -17
  54. data/spec/helpers/form_helper_spec.rb +33 -33
  55. data/spec/helpers/input_helper_spec.rb +975 -2
  56. data/spec/helpers/inputs_helper_spec.rb +120 -105
  57. data/spec/helpers/reflection_helper_spec.rb +3 -3
  58. data/spec/helpers/semantic_errors_helper_spec.rb +22 -22
  59. data/spec/i18n_spec.rb +26 -26
  60. data/spec/input_class_finder_spec.rb +1 -1
  61. data/spec/inputs/base/collections_spec.rb +6 -6
  62. data/spec/inputs/base/validations_spec.rb +157 -19
  63. data/spec/inputs/boolean_input_spec.rb +55 -55
  64. data/spec/inputs/check_boxes_input_spec.rb +96 -95
  65. data/spec/inputs/color_input_spec.rb +51 -63
  66. data/spec/inputs/country_input_spec.rb +20 -20
  67. data/spec/inputs/custom_input_spec.rb +2 -6
  68. data/spec/inputs/datalist_input_spec.rb +1 -1
  69. data/spec/inputs/date_picker_input_spec.rb +42 -42
  70. data/spec/inputs/date_select_input_spec.rb +51 -37
  71. data/spec/inputs/datetime_picker_input_spec.rb +46 -46
  72. data/spec/inputs/datetime_select_input_spec.rb +53 -37
  73. data/spec/inputs/email_input_spec.rb +5 -5
  74. data/spec/inputs/file_input_spec.rb +6 -6
  75. data/spec/inputs/hidden_input_spec.rb +18 -18
  76. data/spec/inputs/include_blank_spec.rb +8 -8
  77. data/spec/inputs/label_spec.rb +20 -20
  78. data/spec/inputs/number_input_spec.rb +112 -112
  79. data/spec/inputs/password_input_spec.rb +5 -5
  80. data/spec/inputs/phone_input_spec.rb +5 -5
  81. data/spec/inputs/placeholder_spec.rb +5 -5
  82. data/spec/inputs/radio_input_spec.rb +63 -65
  83. data/spec/inputs/range_input_spec.rb +66 -66
  84. data/spec/inputs/readonly_spec.rb +4 -4
  85. data/spec/inputs/search_input_spec.rb +5 -5
  86. data/spec/inputs/select_input_spec.rb +92 -96
  87. data/spec/inputs/string_input_spec.rb +23 -23
  88. data/spec/inputs/text_input_spec.rb +16 -16
  89. data/spec/inputs/time_picker_input_spec.rb +43 -43
  90. data/spec/inputs/time_select_input_spec.rb +67 -54
  91. data/spec/inputs/time_zone_input_spec.rb +19 -19
  92. data/spec/inputs/url_input_spec.rb +5 -5
  93. data/spec/inputs/with_options_spec.rb +7 -7
  94. data/spec/localizer_spec.rb +17 -17
  95. data/spec/namespaced_class_finder_spec.rb +2 -2
  96. data/spec/schema.rb +21 -0
  97. data/spec/spec_helper.rb +163 -223
  98. data/spec/support/custom_macros.rb +72 -75
  99. data/spec/support/shared_examples.rb +0 -1301
  100. data/spec/support/test_environment.rb +23 -9
  101. metadata +33 -123
  102. data/Appraisals +0 -43
  103. data/CHANGELOG +0 -54
  104. data/gemfiles/rails_3.2.gemfile +0 -9
  105. data/gemfiles/rails_4.0.4.gemfile +0 -8
  106. data/gemfiles/rails_4.1.gemfile +0 -8
  107. data/gemfiles/rails_4.2.gemfile +0 -8
  108. data/gemfiles/rails_4.gemfile +0 -8
  109. data/gemfiles/rails_5.0.gemfile +0 -8
  110. data/gemfiles/rails_edge.gemfile +0 -15
  111. data/lib/formtastic/util.rb +0 -57
  112. data/spec/helpers/namespaced_action_helper_spec.rb +0 -43
  113. data/spec/helpers/namespaced_input_helper_spec.rb +0 -36
  114. data/spec/util_spec.rb +0 -66
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
  require 'spec_helper'
3
3
 
4
- describe 'FormHelper' do
4
+ RSpec.describe 'FormHelper' do
5
5
 
6
6
  include FormtasticSpecHelper
7
7
 
@@ -14,21 +14,21 @@ describe 'FormHelper' do
14
14
 
15
15
  it 'yields an instance of Formtastic::FormBuilder' do
16
16
  semantic_form_for(@new_post, :url => '/hello') do |builder|
17
- builder.class.should == Formtastic::FormBuilder
17
+ expect(builder.class).to eq(Formtastic::FormBuilder)
18
18
  end
19
19
  end
20
20
 
21
21
  it 'adds a class of "formtastic" to the generated form' do
22
22
  concat(semantic_form_for(@new_post, :url => '/hello') do |builder|
23
23
  end)
24
- output_buffer.should have_tag("form.formtastic")
24
+ expect(output_buffer).to have_tag("form.formtastic")
25
25
  end
26
26
 
27
27
  it 'does not add "novalidate" attribute to the generated form when configured to do so' do
28
28
  with_config :perform_browser_validations, true do
29
29
  concat(semantic_form_for(@new_post, :url => '/hello') do |builder|
30
30
  end)
31
- output_buffer.should_not have_tag("form[@novalidate]")
31
+ expect(output_buffer).not_to have_tag("form[@novalidate]")
32
32
  end
33
33
  end
34
34
 
@@ -36,7 +36,7 @@ describe 'FormHelper' do
36
36
  with_config :perform_browser_validations, false do
37
37
  concat(semantic_form_for(@new_post, :url => '/hello') do |builder|
38
38
  end)
39
- output_buffer.should have_tag("form[@novalidate]")
39
+ expect(output_buffer).to have_tag("form[@novalidate]")
40
40
  end
41
41
  end
42
42
 
@@ -44,7 +44,7 @@ describe 'FormHelper' do
44
44
  with_config :perform_browser_validations, false do
45
45
  concat(semantic_form_for(@new_post, :url => '/hello', :html => { :novalidate => true }) do |builder|
46
46
  end)
47
- output_buffer.should have_tag("form[@novalidate]")
47
+ expect(output_buffer).to have_tag("form[@novalidate]")
48
48
  end
49
49
  end
50
50
 
@@ -52,7 +52,7 @@ describe 'FormHelper' do
52
52
  with_config :perform_browser_validations, true do
53
53
  concat(semantic_form_for(@new_post, :url => '/hello', :html => { :novalidate => false }) do |builder|
54
54
  end)
55
- output_buffer.should_not have_tag("form[@novalidate]")
55
+ expect(output_buffer).not_to have_tag("form[@novalidate]")
56
56
  end
57
57
  end
58
58
 
@@ -60,103 +60,103 @@ describe 'FormHelper' do
60
60
  Formtastic::Helpers::FormHelper.default_form_class = 'xyz'
61
61
  concat(semantic_form_for(::Post.new, :as => :post, :url => '/hello') do |builder|
62
62
  end)
63
- output_buffer.should have_tag("form.xyz")
63
+ expect(output_buffer).to have_tag("form.xyz")
64
64
  end
65
65
 
66
66
  it 'omits the leading spaces from the classes in the generated form when the default class is nil' do
67
67
  Formtastic::Helpers::FormHelper.default_form_class = nil
68
68
  concat(semantic_form_for(::Post.new, :as => :post, :url => '/hello') do |builder|
69
69
  end)
70
- output_buffer.should have_tag("form[class='post']")
70
+ expect(output_buffer).to have_tag("form[class='post']")
71
71
  end
72
72
 
73
73
  it 'adds class matching the object name to the generated form when a symbol is provided' do
74
74
  concat(semantic_form_for(@new_post, :url => '/hello') do |builder|
75
75
  end)
76
- output_buffer.should have_tag("form.post")
76
+ expect(output_buffer).to have_tag("form.post")
77
77
 
78
78
  concat(semantic_form_for(:project, :url => '/hello') do |builder|
79
79
  end)
80
- output_buffer.should have_tag("form.project")
80
+ expect(output_buffer).to have_tag("form.project")
81
81
  end
82
82
 
83
83
  it 'adds class matching the :as option when provided' do
84
84
  concat(semantic_form_for(@new_post, :as => :message, :url => '/hello') do |builder|
85
85
  end)
86
- output_buffer.should have_tag("form.message")
86
+ expect(output_buffer).to have_tag("form.message")
87
87
 
88
88
  concat(semantic_form_for([:admins, @new_post], :as => :message, :url => '/hello') do |builder|
89
89
  end)
90
- output_buffer.should have_tag("form.message")
90
+ expect(output_buffer).to have_tag("form.message")
91
91
  end
92
92
 
93
93
  it 'adds class matching the object\'s class to the generated form when an object is provided' do
94
94
  concat(semantic_form_for(@new_post) do |builder|
95
95
  end)
96
- output_buffer.should have_tag("form.post")
96
+ expect(output_buffer).to have_tag("form.post")
97
97
  end
98
98
 
99
99
  it 'adds a namespaced class to the generated form' do
100
100
  concat(semantic_form_for(::Namespaced::Post.new, :url => '/hello') do |builder|
101
101
  end)
102
- output_buffer.should have_tag("form.namespaced_post")
102
+ expect(output_buffer).to have_tag("form.namespaced_post")
103
103
  end
104
104
 
105
105
  it 'adds a customized class to the generated form' do
106
106
  Formtastic::Helpers::FormHelper.default_form_model_class_proc = lambda { |model_class_name| "#{model_class_name}_form" }
107
107
  concat(semantic_form_for(@new_post, :url => '/hello') do |builder|
108
108
  end)
109
- output_buffer.should have_tag("form.post_form")
109
+ expect(output_buffer).to have_tag("form.post_form")
110
110
 
111
111
  concat(semantic_form_for(:project, :url => '/hello') do |builder|
112
112
  end)
113
- output_buffer.should have_tag("form.project_form")
113
+ expect(output_buffer).to have_tag("form.project_form")
114
114
  end
115
115
 
116
116
  describe 'allows :html options' do
117
- before(:each) do
117
+ before(:example) do
118
118
  concat(semantic_form_for(@new_post, :url => '/hello', :html => { :id => "something-special", :class => "something-extra", :multipart => true }) do |builder|
119
119
  end)
120
120
  end
121
121
 
122
122
  it 'to add a id of "something-special" to generated form' do
123
- output_buffer.should have_tag("form#something-special")
123
+ expect(output_buffer).to have_tag("form#something-special")
124
124
  end
125
125
 
126
126
  it 'to add a class of "something-extra" to generated form' do
127
- output_buffer.should have_tag("form.something-extra")
127
+ expect(output_buffer).to have_tag("form.something-extra")
128
128
  end
129
129
 
130
130
  it 'to add enctype="multipart/form-data"' do
131
- output_buffer.should have_tag('form[@enctype="multipart/form-data"]')
131
+ expect(output_buffer).to have_tag('form[@enctype="multipart/form-data"]')
132
132
  end
133
133
  end
134
134
 
135
135
  it 'can be called with a resource-oriented style' do
136
136
  semantic_form_for(@new_post) do |builder|
137
- builder.object.class.should == ::Post
138
- builder.object_name.should == "post"
137
+ expect(builder.object.class).to eq(::Post)
138
+ expect(builder.object_name).to eq("post")
139
139
  end
140
140
  end
141
141
 
142
142
  it 'can be called with a generic style and instance variable' do
143
143
  semantic_form_for(@new_post, :as => :post, :url => new_post_path) do |builder|
144
- builder.object.class.should == ::Post
145
- builder.object_name.to_s.should == "post" # TODO: is this forced .to_s a bad assumption somewhere?
144
+ expect(builder.object.class).to eq(::Post)
145
+ expect(builder.object_name.to_s).to eq("post") # TODO: is this forced .to_s a bad assumption somewhere?
146
146
  end
147
147
  end
148
148
 
149
149
  it 'can be called with a generic style and inline object' do
150
150
  semantic_form_for(@new_post, :url => new_post_path) do |builder|
151
- builder.object.class.should == ::Post
152
- builder.object_name.to_s.should == "post" # TODO: is this forced .to_s a bad assumption somewhere?
151
+ expect(builder.object.class).to eq(::Post)
152
+ expect(builder.object_name.to_s).to eq("post") # TODO: is this forced .to_s a bad assumption somewhere?
153
153
  end
154
154
  end
155
155
 
156
156
  describe 'ActionView::Base.field_error_proc' do
157
157
  it 'is set to no-op wrapper by default' do
158
158
  semantic_form_for(@new_post, :url => '/hello') do |builder|
159
- ::ActionView::Base.field_error_proc.call("html", nil).should == "html"
159
+ expect(::ActionView::Base.field_error_proc.call("html", nil)).to eq("html")
160
160
  end
161
161
  end
162
162
 
@@ -164,15 +164,15 @@ describe 'FormHelper' do
164
164
  field_error_proc = double()
165
165
  Formtastic::Helpers::FormHelper.formtastic_field_error_proc = field_error_proc
166
166
  semantic_form_for(@new_post, :url => '/hello') do |builder|
167
- ::ActionView::Base.field_error_proc.should == field_error_proc
167
+ expect(::ActionView::Base.field_error_proc).to eq(field_error_proc)
168
168
  end
169
169
  end
170
170
 
171
171
  it 'is restored to its original value after the form is rendered' do
172
- lambda do
172
+ expect do
173
173
  Formtastic::Helpers::FormHelper.formtastic_field_error_proc = proc {""}
174
174
  semantic_form_for(@new_post, :url => '/hello') { |builder| }
175
- end.should_not change(::ActionView::Base, :field_error_proc)
175
+ end.not_to change(::ActionView::Base, :field_error_proc)
176
176
  end
177
177
  end
178
178
 
@@ -181,7 +181,7 @@ describe 'FormHelper' do
181
181
  class MyAwesomeCustomBuilder < Formtastic::FormBuilder
182
182
  end
183
183
  semantic_form_for(@new_post, :url => '/hello', :builder => MyAwesomeCustomBuilder) do |builder|
184
- builder.class.should == MyAwesomeCustomBuilder
184
+ expect(builder.class).to eq(MyAwesomeCustomBuilder)
185
185
  end
186
186
  end
187
187
  end
@@ -209,7 +209,7 @@ describe 'FormHelper' do
209
209
  describe '#semantic_fields_for' do
210
210
  it 'yields an instance of Formtastic::FormBuilder' do
211
211
  semantic_fields_for(@new_post) do |builder|
212
- builder.class.should.kind_of?(Formtastic::FormBuilder)
212
+ expect(builder.class).to be(Formtastic::FormBuilder)
213
213
  end
214
214
  end
215
215
  end
@@ -1,6 +1,979 @@
1
1
  # encoding: utf-8
2
2
  require 'spec_helper'
3
3
 
4
- describe 'Formtastic::FormBuilder#input' do
5
- it_behaves_like 'Input Helper' # from spec/support/shared_examples.rb
4
+ RSpec.describe 'with input class finder' do
5
+ include_context 'form builder'
6
+
7
+ before do
8
+ @errors = double('errors')
9
+ allow(@errors).to receive(:[]).and_return([])
10
+ allow(@new_post).to receive(:errors).and_return(@errors)
11
+ end
12
+
13
+ describe 'arguments and options' do
14
+
15
+ it 'should require the first argument (the method on form\'s object)' do
16
+ expect {
17
+ concat(semantic_form_for(@new_post) do |builder|
18
+ concat(builder.input()) # no args passed in at all
19
+ end)
20
+ }.to raise_error(ArgumentError)
21
+ end
22
+
23
+ describe ':required option' do
24
+
25
+ describe 'when true' do
26
+
27
+ it 'should set a "required" class' do
28
+ with_config :required_string, " required yo!" do
29
+ concat(semantic_form_for(@new_post) do |builder|
30
+ concat(builder.input(:title, :required => true))
31
+ end)
32
+ expect(output_buffer).not_to have_tag('form li.optional')
33
+ expect(output_buffer).to have_tag('form li.required')
34
+ end
35
+ end
36
+
37
+ it 'should append the "required" string to the label' do
38
+ with_config :required_string, " required yo!" do
39
+ concat(semantic_form_for(@new_post) do |builder|
40
+ concat(builder.input(:title, :required => true))
41
+ end)
42
+ expect(output_buffer).to have_tag('form li.required label', :text => /required yo/)
43
+ end
44
+ end
45
+ end
46
+
47
+ describe 'when false' do
48
+
49
+ before do
50
+ @string = Formtastic::FormBuilder.optional_string = " optional yo!" # ensure there's something in the string
51
+ expect(@new_post.class).not_to receive(:reflect_on_all_validations)
52
+ end
53
+
54
+ after do
55
+ Formtastic::FormBuilder.optional_string = ''
56
+ end
57
+
58
+ it 'should set an "optional" class' do
59
+ concat(semantic_form_for(@new_post) do |builder|
60
+ concat(builder.input(:title, :required => false))
61
+ end)
62
+ expect(output_buffer).not_to have_tag('form li.required')
63
+ expect(output_buffer).to have_tag('form li.optional')
64
+ end
65
+
66
+ it 'should set and "optional" class also when there is presence validator' do
67
+ expect(@new_post.class).to receive(:validators_on).with(:title).at_least(:once).and_return([
68
+ active_model_presence_validator([:title])
69
+ ])
70
+ concat(semantic_form_for(@new_post) do |builder|
71
+ concat(builder.input(:title, :required => false))
72
+ end)
73
+ expect(output_buffer).not_to have_tag('form li.required')
74
+ expect(output_buffer).to have_tag('form li.optional')
75
+ end
76
+
77
+ it 'should append the "optional" string to the label' do
78
+ concat(semantic_form_for(@new_post) do |builder|
79
+ concat(builder.input(:title, :required => false))
80
+ end)
81
+ expect(output_buffer).to have_tag('form li.optional label', :text => /#{@string}$/)
82
+ end
83
+
84
+ end
85
+
86
+ describe 'when not provided' do
87
+
88
+ describe 'and an object was not given' do
89
+
90
+ it 'should use the default value' do
91
+ expect(Formtastic::FormBuilder.all_fields_required_by_default).to eq(true)
92
+ Formtastic::FormBuilder.all_fields_required_by_default = false
93
+
94
+ concat(semantic_form_for(:project, :url => 'http://test.host/') do |builder|
95
+ concat(builder.input(:title))
96
+ end)
97
+ expect(output_buffer).not_to have_tag('form li.required')
98
+ expect(output_buffer).to have_tag('form li.optional')
99
+
100
+ Formtastic::FormBuilder.all_fields_required_by_default = true
101
+ end
102
+
103
+ end
104
+
105
+ describe 'and an object with :validators_on was given (ActiveModel, Active Resource)' do
106
+ before do
107
+ allow(@new_post).to receive(:class).and_return(::PostModel)
108
+ end
109
+
110
+ after do
111
+ allow(@new_post).to receive(:class).and_return(::Post)
112
+ end
113
+ describe 'and validates_presence_of was called for the method' do
114
+ it 'should be required' do
115
+
116
+ expect(@new_post.class).to receive(:validators_on).with(:title).at_least(:once).and_return([
117
+ active_model_presence_validator([:title])
118
+ ])
119
+
120
+ expect(@new_post.class).to receive(:validators_on).with(:body).at_least(:once).and_return([
121
+ active_model_presence_validator([:body], {:if => true})
122
+ ])
123
+
124
+ concat(semantic_form_for(@new_post) do |builder|
125
+ concat(builder.input(:title))
126
+ concat(builder.input(:body))
127
+ end)
128
+ expect(output_buffer).to have_tag('form li.required')
129
+ expect(output_buffer).not_to have_tag('form li.optional')
130
+ end
131
+
132
+ it 'should be required when there is :on => :create option on create' do
133
+ with_config :required_string, " required yo!" do
134
+ expect(@new_post.class).to receive(:validators_on).with(:title).at_least(:once).and_return([
135
+ active_model_presence_validator([:title], {:on => :create})
136
+ ])
137
+ concat(semantic_form_for(@new_post) do |builder|
138
+ concat(builder.input(:title))
139
+ end)
140
+ expect(output_buffer).to have_tag('form li.required')
141
+ expect(output_buffer).not_to have_tag('form li.optional')
142
+ end
143
+ end
144
+
145
+ it 'should be required when there is :create option in validation contexts array on create' do
146
+ with_config :required_string, " required yo!" do
147
+ expect(@new_post.class).to receive(:validators_on).with(:title).at_least(:once).and_return([
148
+ active_model_presence_validator([:title], {:on => [:create]})
149
+ ])
150
+ concat(semantic_form_for(@new_post) do |builder|
151
+ concat(builder.input(:title))
152
+ end)
153
+ expect(output_buffer).to have_tag('form li.required')
154
+ expect(output_buffer).not_to have_tag('form li.optional')
155
+ end
156
+ end
157
+
158
+ it 'should be required when there is :on => :save option on create' do
159
+ with_config :required_string, " required yo!" do
160
+ expect(@new_post.class).to receive(:validators_on).with(:title).at_least(:once).and_return([
161
+ active_model_presence_validator([:title], {:on => :save})
162
+ ])
163
+ concat(semantic_form_for(@new_post) do |builder|
164
+ concat(builder.input(:title))
165
+ end)
166
+ expect(output_buffer).to have_tag('form li.required')
167
+ expect(output_buffer).not_to have_tag('form li.optional')
168
+ end
169
+ end
170
+
171
+ it 'should be required when there is :save option in validation contexts array on create' do
172
+ with_config :required_string, " required yo!" do
173
+ expect(@new_post.class).to receive(:validators_on).with(:title).at_least(:once).and_return([
174
+ active_model_presence_validator([:title], {:on => [:save]})
175
+ ])
176
+ concat(semantic_form_for(@new_post) do |builder|
177
+ concat(builder.input(:title))
178
+ end)
179
+ expect(output_buffer).to have_tag('form li.required')
180
+ expect(output_buffer).not_to have_tag('form li.optional')
181
+ end
182
+ end
183
+
184
+ it 'should be required when there is :on => :save option on update' do
185
+ with_config :required_string, " required yo!" do
186
+ expect(@fred.class).to receive(:validators_on).with(:login).at_least(:once).and_return([
187
+ active_model_presence_validator([:login], {:on => :save})
188
+ ])
189
+ concat(semantic_form_for(@fred) do |builder|
190
+ concat(builder.input(:login))
191
+ end)
192
+ expect(output_buffer).to have_tag('form li.required')
193
+ expect(output_buffer).not_to have_tag('form li.optional')
194
+ end
195
+ end
196
+
197
+ it 'should be required when there is :save option in validation contexts array on update' do
198
+ with_config :required_string, " required yo!" do
199
+ expect(@fred.class).to receive(:validators_on).with(:login).at_least(:once).and_return([
200
+ active_model_presence_validator([:login], {:on => [:save]})
201
+ ])
202
+ concat(semantic_form_for(@fred) do |builder|
203
+ concat(builder.input(:login))
204
+ end)
205
+ expect(output_buffer).to have_tag('form li.required')
206
+ expect(output_buffer).not_to have_tag('form li.optional')
207
+ end
208
+ end
209
+
210
+ it 'should not be required when there is :on => :create option on update' do
211
+ expect(@fred.class).to receive(:validators_on).with(:login).at_least(:once).and_return([
212
+ active_model_presence_validator([:login], {:on => :create})
213
+ ])
214
+ concat(semantic_form_for(@fred) do |builder|
215
+ concat(builder.input(:login))
216
+ end)
217
+ expect(output_buffer).not_to have_tag('form li.required')
218
+ expect(output_buffer).to have_tag('form li.optional')
219
+ end
220
+
221
+ it 'should not be required when there is :create option in validation contexts array on update' do
222
+ expect(@fred.class).to receive(:validators_on).with(:login).at_least(:once).and_return([
223
+ active_model_presence_validator([:login], {:on => [:create]})
224
+ ])
225
+ concat(semantic_form_for(@fred) do |builder|
226
+ concat(builder.input(:login))
227
+ end)
228
+ expect(output_buffer).not_to have_tag('form li.required')
229
+ expect(output_buffer).to have_tag('form li.optional')
230
+ end
231
+
232
+ it 'should not be required when there is :on => :update option on create' do
233
+ expect(@new_post.class).to receive(:validators_on).with(:title).at_least(:once).and_return([
234
+ active_model_presence_validator([:title], {:on => :update})
235
+ ])
236
+ concat(semantic_form_for(@new_post) do |builder|
237
+ concat(builder.input(:title))
238
+ end)
239
+ expect(output_buffer).not_to have_tag('form li.required')
240
+ expect(output_buffer).to have_tag('form li.optional')
241
+ end
242
+
243
+ it 'should not be required when there is :update option in validation contexts array on create' do
244
+ expect(@new_post.class).to receive(:validators_on).with(:title).at_least(:once).and_return([
245
+ active_model_presence_validator([:title], {:on => [:update]})
246
+ ])
247
+ concat(semantic_form_for(@new_post) do |builder|
248
+ concat(builder.input(:title))
249
+ end)
250
+ expect(output_buffer).not_to have_tag('form li.required')
251
+ expect(output_buffer).to have_tag('form li.optional')
252
+ end
253
+
254
+ it 'should be not be required if the optional :if condition is not satisifed' do
255
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => false })
256
+ end
257
+
258
+ it 'should not be required if the optional :if proc evaluates to false' do
259
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => proc { |record| false } })
260
+ end
261
+
262
+ it 'should be required if the optional :if proc evaluates to true' do
263
+ presence_should_be_required(:required => true, :tag => :body, :options => { :if => proc { |record| true } })
264
+ end
265
+
266
+ it 'should not be required if the optional :unless proc evaluates to true' do
267
+ presence_should_be_required(:required => false, :tag => :body, :options => { :unless => proc { |record| true } })
268
+ end
269
+
270
+ it 'should be required if the optional :unless proc evaluates to false' do
271
+ presence_should_be_required(:required => true, :tag => :body, :options => { :unless => proc { |record| false } })
272
+ end
273
+
274
+ it 'should be required if the optional :if with a method string evaluates to true' do
275
+ expect(@new_post).to receive(:required_condition).and_return(true)
276
+ presence_should_be_required(:required => true, :tag => :body, :options => { :if => :required_condition })
277
+ end
278
+
279
+ it 'should be required if the optional :if with a method string evaluates to false' do
280
+ expect(@new_post).to receive(:required_condition).and_return(false)
281
+ presence_should_be_required(:required => false, :tag => :body, :options => { :if => :required_condition })
282
+ end
283
+
284
+ it 'should be required if the optional :unless with a method string evaluates to false' do
285
+ expect(@new_post).to receive(:required_condition).and_return(false)
286
+ presence_should_be_required(:required => true, :tag => :body, :options => { :unless => :required_condition })
287
+ end
288
+
289
+ it 'should not be required if the optional :unless with a method string evaluates to true' do
290
+ expect(@new_post).to receive(:required_condition).and_return(true)
291
+ presence_should_be_required(:required => false, :tag => :body, :options => { :unless => :required_condition })
292
+ end
293
+ end
294
+
295
+ describe 'and validates_inclusion_of was called for the method' do
296
+ it 'should be required' do
297
+ expect(@new_post.class).to receive(:validators_on).with(:published).at_least(:once).and_return([
298
+ active_model_inclusion_validator([:published], {:in => [false, true]})
299
+ ])
300
+ should_be_required(:tag => :published, :required => true)
301
+ end
302
+
303
+ it 'should not be required if allow_blank is true' do
304
+ expect(@new_post.class).to receive(:validators_on).with(:published).at_least(:once).and_return([
305
+ active_model_inclusion_validator([:published], {:in => [false, true], :allow_blank => true})
306
+ ])
307
+ should_be_required(:tag => :published, :required => false)
308
+ end
309
+ end
310
+
311
+ describe 'and validates_length_of was called for the method' do
312
+ it 'should be required if minimum is set' do
313
+ length_should_be_required(:tag => :title, :required => true, :options => {:minimum => 1})
314
+ end
315
+
316
+ it 'should be required if :within is set' do
317
+ length_should_be_required(:tag => :title, :required => true, :options => {:within => 1..5})
318
+ end
319
+
320
+ it 'should not be required if :within allows zero length' do
321
+ length_should_be_required(:tag => :title, :required => false, :options => {:within => 0..5})
322
+ end
323
+
324
+ it 'should not be required if only :minimum is zero' do
325
+ length_should_be_required(:tag => :title, :required => false, :options => {:minimum => 0})
326
+ end
327
+
328
+ it 'should not be required if only :minimum is not set' do
329
+ length_should_be_required(:tag => :title, :required => false, :options => {:maximum => 5})
330
+ end
331
+
332
+ it 'should not be required if allow_blank is true' do
333
+ length_should_be_required(:tag => :published, :required => false, :options => {:allow_blank => true})
334
+ end
335
+ end
336
+
337
+ def add_presence_validator(options)
338
+ allow(@new_post.class).to receive(:validators_on).with(options[:tag]).and_return([
339
+ active_model_presence_validator([options[:tag]], options[:options])
340
+ ])
341
+ end
342
+
343
+ def add_length_validator(options)
344
+ expect(@new_post.class).to receive(:validators_on).with(options[:tag]).at_least(:once) {[
345
+ active_model_length_validator([options[:tag]], options[:options])
346
+ ]}
347
+ end
348
+
349
+ # TODO make a matcher for this?
350
+ def should_be_required(options)
351
+ concat(semantic_form_for(@new_post) do |builder|
352
+ concat(builder.input(options[:tag]))
353
+ end)
354
+
355
+ if options[:required]
356
+ expect(output_buffer).not_to have_tag('form li.optional')
357
+ expect(output_buffer).to have_tag('form li.required')
358
+ else
359
+ expect(output_buffer).to have_tag('form li.optional')
360
+ expect(output_buffer).not_to have_tag('form li.required')
361
+ end
362
+ end
363
+
364
+ def presence_should_be_required(options)
365
+ add_presence_validator(options)
366
+ should_be_required(options)
367
+ end
368
+
369
+ def length_should_be_required(options)
370
+ add_length_validator(options)
371
+ should_be_required(options)
372
+ end
373
+
374
+ # TODO JF reversed this during refactor, need to make sure
375
+ describe 'and there are no requirement validations on the method' do
376
+ before do
377
+ expect(@new_post.class).to receive(:validators_on).with(:title).and_return([])
378
+ end
379
+
380
+ it 'should not be required' do
381
+ concat(semantic_form_for(@new_post) do |builder|
382
+ concat(builder.input(:title))
383
+ end)
384
+ expect(output_buffer).not_to have_tag('form li.required')
385
+ expect(output_buffer).to have_tag('form li.optional')
386
+ end
387
+ end
388
+
389
+ end
390
+
391
+ describe 'and an object without :validators_on' do
392
+
393
+ it 'should use the default value' do
394
+ expect(Formtastic::FormBuilder.all_fields_required_by_default).to eq(true)
395
+ Formtastic::FormBuilder.all_fields_required_by_default = false
396
+
397
+ concat(semantic_form_for(@new_post) do |builder|
398
+ concat(builder.input(:title))
399
+ end)
400
+ expect(output_buffer).not_to have_tag('form li.required')
401
+ expect(output_buffer).to have_tag('form li.optional')
402
+
403
+ Formtastic::FormBuilder.all_fields_required_by_default = true
404
+ end
405
+
406
+ end
407
+
408
+ end
409
+
410
+ end
411
+
412
+ describe ':as option' do
413
+
414
+ describe 'when not provided' do
415
+
416
+ it 'should default to a string for forms without objects unless column is password' do
417
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
418
+ concat(builder.input(:anything))
419
+ end)
420
+ expect(output_buffer).to have_tag('form li.string')
421
+ end
422
+
423
+ it 'should default to password for forms without objects if column is password' do
424
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
425
+ concat(builder.input(:password))
426
+ concat(builder.input(:password_confirmation))
427
+ concat(builder.input(:confirm_password))
428
+ end)
429
+ expect(output_buffer).to have_tag('form li.password', :count => 3)
430
+ end
431
+
432
+ it 'should default to a string for methods on objects that don\'t respond to "column_for_attribute"' do
433
+ allow(@new_post).to receive(:method_without_a_database_column)
434
+ allow(@new_post).to receive(:column_for_attribute).and_return(nil)
435
+ expect(default_input_type(nil, :method_without_a_database_column)).to eq(:string)
436
+ end
437
+
438
+ it 'should default to :password for methods that don\'t have a column in the database but "password" is in the method name' do
439
+ allow(@new_post).to receive(:password_method_without_a_database_column)
440
+ allow(@new_post).to receive(:column_for_attribute).and_return(nil)
441
+ expect(default_input_type(nil, :password_method_without_a_database_column)).to eq(:password)
442
+ end
443
+
444
+ 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
445
+ allow(@new_post).to receive(:password_method_without_a_database_column)
446
+ allow(@new_post).to receive(:column_for_attribute).and_return(nil)
447
+ expect(default_input_type(nil, :password_method_without_a_database_column)).to eq(:password)
448
+ end
449
+
450
+ it 'should default to :number for "integer" column with name ending in "_id"' do
451
+ allow(@new_post).to receive(:aws_instance_id)
452
+ allow(@new_post).to receive(:column_for_attribute).with(:aws_instance_id).and_return(double('column', :type => :integer))
453
+ expect(default_input_type(:integer, :aws_instance_id)).to eq(:number)
454
+ end
455
+
456
+ it 'should default to :select for associations' do
457
+ allow(@new_post.class).to receive(:reflect_on_association).with(:user_id).and_return(double('ActiveRecord::Reflection::AssociationReflection'))
458
+ allow(@new_post.class).to receive(:reflect_on_association).with(:section_id).and_return(double('ActiveRecord::Reflection::AssociationReflection'))
459
+ expect(default_input_type(:integer, :user_id)).to eq(:select)
460
+ expect(default_input_type(:integer, :section_id)).to eq(:select)
461
+ end
462
+
463
+ it 'should default to :select for enum' do
464
+ statuses = ActiveSupport::HashWithIndifferentAccess.new("active"=>0, "inactive"=>1)
465
+ allow(@new_post.class).to receive(:statuses) { statuses }
466
+ allow(@new_post).to receive(:defined_enums) { {"status" => statuses } }
467
+
468
+ expect(default_input_type(:integer, :status)).to eq(:select)
469
+ end
470
+
471
+ it 'should default to :password for :string column types with "password" in the method name' do
472
+ expect(default_input_type(:string, :password)).to eq(:password)
473
+ expect(default_input_type(:string, :hashed_password)).to eq(:password)
474
+ expect(default_input_type(:string, :password_hash)).to eq(:password)
475
+ end
476
+
477
+ it 'should default to :text for :text column types' do
478
+ expect(default_input_type(:text)).to eq(:text)
479
+ end
480
+
481
+ it 'should default to :date_select for :date column types' do
482
+ expect(default_input_type(:date)).to eq(:date_select)
483
+ end
484
+
485
+ it 'should default to :text for :hstore, :json and :jsonb column types' do
486
+ expect(default_input_type(:hstore)).to eq(:text)
487
+ expect(default_input_type(:json)).to eq(:text)
488
+ expect(default_input_type(:jsonb)).to eq(:text)
489
+ end
490
+
491
+ it 'should default to :datetime_select for :datetime and :timestamp column types' do
492
+ expect(default_input_type(:datetime)).to eq(:datetime_select)
493
+ expect(default_input_type(:timestamp)).to eq(:datetime_select)
494
+ end
495
+
496
+ it 'should default to :time_select for :time column types' do
497
+ expect(default_input_type(:time)).to eq(:time_select)
498
+ end
499
+
500
+ it 'should default to :boolean for :boolean column types' do
501
+ expect(default_input_type(:boolean)).to eq(:boolean)
502
+ end
503
+
504
+ it 'should default to :string for :string column types' do
505
+ expect(default_input_type(:string)).to eq(:string)
506
+ end
507
+
508
+ it 'should default to :string for :citext column types' do
509
+ expect(default_input_type(:citext)).to eq(:string)
510
+ end
511
+
512
+ it 'should default to :string for :inet column types' do
513
+ expect(default_input_type(:inet)).to eq(:string)
514
+ end
515
+
516
+ it 'should default to :number for :integer, :float and :decimal column types' do
517
+ expect(default_input_type(:integer)).to eq(:number)
518
+ expect(default_input_type(:float)).to eq(:number)
519
+ expect(default_input_type(:decimal)).to eq(:number)
520
+ end
521
+
522
+ it 'should default to :country for :string columns named country' do
523
+ expect(default_input_type(:string, :country)).to eq(:country)
524
+ end
525
+
526
+ it 'should default to :email for :string columns matching email' do
527
+ expect(default_input_type(:string, :email)).to eq(:email)
528
+ expect(default_input_type(:string, :customer_email)).to eq(:email)
529
+ expect(default_input_type(:string, :email_work)).to eq(:email)
530
+ end
531
+
532
+ it 'should default to :url for :string columns named url or website' do
533
+ expect(default_input_type(:string, :url)).to eq(:url)
534
+ expect(default_input_type(:string, :website)).to eq(:url)
535
+ expect(default_input_type(:string, :my_url)).to eq(:url)
536
+ expect(default_input_type(:string, :hurl)).not_to eq(:url)
537
+ end
538
+
539
+ it 'should default to :phone for :string columns named phone or fax' do
540
+ expect(default_input_type(:string, :phone)).to eq(:phone)
541
+ expect(default_input_type(:string, :fax)).to eq(:phone)
542
+ end
543
+
544
+ it 'should default to :search for :string columns named search' do
545
+ expect(default_input_type(:string, :search)).to eq(:search)
546
+ end
547
+
548
+ it 'should default to :color for :string columns matching color' do
549
+ expect(default_input_type(:string, :color)).to eq(:color)
550
+ expect(default_input_type(:string, :user_color)).to eq(:color)
551
+ expect(default_input_type(:string, :color_for_image)).to eq(:color)
552
+ end
553
+
554
+ describe 'defaulting to file column' do
555
+ Formtastic::FormBuilder.file_methods.each do |method|
556
+ it "should default to :file for attributes that respond to ##{method}" do
557
+ column = double('column')
558
+
559
+ Formtastic::FormBuilder.file_methods.each do |test|
560
+ ### TODO: Check if this is ok
561
+ allow(column).to receive(method).with(test).and_return(method == test)
562
+ end
563
+
564
+ expect(@new_post).to receive(method).and_return(column)
565
+
566
+ semantic_form_for(@new_post) do |builder|
567
+ expect(builder.send(:default_input_type, method)).to eq(:file)
568
+ end
569
+ end
570
+ end
571
+
572
+ end
573
+ end
574
+
575
+ it 'should call the corresponding input class with .to_html' do
576
+ [:select, :time_zone, :radio, :date_select, :datetime_select, :time_select, :boolean, :check_boxes, :hidden, :string, :password, :number, :text, :file].each do |input_style|
577
+ allow(@new_post).to receive(:generic_column_name)
578
+ allow(@new_post).to receive(:column_for_attribute).and_return(double('column', :type => :string, :limit => 255))
579
+ semantic_form_for(@new_post) do |builder|
580
+ input_instance = double('Input instance')
581
+ input_class = "#{input_style.to_s}_input".classify
582
+ input_constant = "Formtastic::Inputs::#{input_class}".constantize
583
+
584
+ expect(input_constant).to receive(:new).and_return(input_instance)
585
+ expect(input_instance).to receive(:to_html).and_return("some HTML")
586
+
587
+ concat(builder.input(:generic_column_name, :as => input_style))
588
+ end
589
+ end
590
+ end
591
+
592
+ end
593
+
594
+ describe ':label option' do
595
+
596
+ describe 'when provided' do
597
+ it 'should be passed down to the label tag' do
598
+ concat(semantic_form_for(@new_post) do |builder|
599
+ concat(builder.input(:title, :label => "Kustom"))
600
+ end)
601
+ expect(output_buffer).to have_tag("form li label", :text => /Kustom/)
602
+ end
603
+
604
+ it 'should not generate a label if false' do
605
+ concat(semantic_form_for(@new_post) do |builder|
606
+ concat(builder.input(:title, :label => false))
607
+ end)
608
+ expect(output_buffer).not_to have_tag("form li label")
609
+ end
610
+
611
+ it 'should be dupped if frozen' do
612
+ concat(semantic_form_for(@new_post) do |builder|
613
+ concat(builder.input(:title, :label => "Kustom".freeze))
614
+ end)
615
+ expect(output_buffer).to have_tag("form li label", :text => /Kustom/)
616
+ end
617
+ end
618
+
619
+ describe 'when not provided' do
620
+ describe 'when localized label is provided' do
621
+ describe 'and object is given' do
622
+ describe 'and label_str_method not :humanize' do
623
+ it 'should render a label with localized text and not apply the label_str_method' do
624
+ with_config :label_str_method, :reverse do
625
+ @localized_label_text = 'Localized title'
626
+ allow(@new_post).to receive(:meta_description)
627
+ ::I18n.backend.store_translations :en,
628
+ :formtastic => {
629
+ :labels => {
630
+ :meta_description => @localized_label_text
631
+ }
632
+ }
633
+
634
+ concat(semantic_form_for(@new_post) do |builder|
635
+ concat(builder.input(:meta_description))
636
+ end)
637
+ expect(output_buffer).to have_tag('form li label', :text => /Localized title/)
638
+ end
639
+ end
640
+ end
641
+ end
642
+ end
643
+
644
+ describe 'when localized label is NOT provided' do
645
+ describe 'and object is not given' do
646
+ it 'should default the humanized method name, passing it down to the label tag' do
647
+ ::I18n.backend.store_translations :en, :formtastic => {}
648
+ with_config :label_str_method, :humanize do
649
+ concat(semantic_form_for(:project, :url => 'http://test.host') do |builder|
650
+ concat(builder.input(:meta_description))
651
+ end)
652
+ expect(output_buffer).to have_tag("form li label", :text => /#{'meta_description'.humanize}/)
653
+ end
654
+ end
655
+ end
656
+
657
+ describe 'and object is given' do
658
+ it 'should delegate the label logic to class human attribute name and pass it down to the label tag' do
659
+ allow(@new_post).to receive(:meta_description) # a two word method name
660
+ expect(@new_post.class).to receive(:human_attribute_name).with('meta_description').and_return('meta_description'.humanize)
661
+
662
+ concat(semantic_form_for(@new_post) do |builder|
663
+ concat(builder.input(:meta_description))
664
+ end)
665
+ expect(output_buffer).to have_tag("form li label", :text => /#{'meta_description'.humanize}/)
666
+ end
667
+ end
668
+
669
+ describe 'and object is given with label_str_method set to :capitalize' do
670
+ it 'should capitalize method name, passing it down to the label tag' do
671
+ with_config :label_str_method, :capitalize do
672
+ allow(@new_post).to receive(:meta_description)
673
+
674
+ concat(semantic_form_for(@new_post) do |builder|
675
+ concat(builder.input(:meta_description))
676
+ end)
677
+ expect(output_buffer).to have_tag("form li label", :text => /#{'meta_description'.capitalize}/)
678
+ end
679
+ end
680
+ end
681
+ end
682
+
683
+ describe 'when localized label is provided' do
684
+ before do
685
+ @localized_label_text = 'Localized title'
686
+ @default_localized_label_text = 'Default localized title'
687
+ ::I18n.backend.store_translations :en,
688
+ :formtastic => {
689
+ :labels => {
690
+ :title => @default_localized_label_text,
691
+ :published => @default_localized_label_text,
692
+ :post => {
693
+ :title => @localized_label_text,
694
+ :published => @default_localized_label_text
695
+ }
696
+ }
697
+ }
698
+ end
699
+
700
+ it 'should render a label with localized label (I18n)' do
701
+ with_config :i18n_lookups_by_default, false do
702
+ concat(semantic_form_for(@new_post) do |builder|
703
+ concat(builder.input(:title, :label => true))
704
+ concat(builder.input(:published, :as => :boolean, :label => true))
705
+ end)
706
+ expect(output_buffer).to have_tag('form li label', :text => Regexp.new('^' + @localized_label_text))
707
+ end
708
+ end
709
+
710
+ it 'should render a hint paragraph containing an optional localized label (I18n) if first is not set' do
711
+ with_config :i18n_lookups_by_default, false do
712
+ ::I18n.backend.store_translations :en,
713
+ :formtastic => {
714
+ :labels => {
715
+ :post => {
716
+ :title => nil,
717
+ :published => nil
718
+ }
719
+ }
720
+ }
721
+ concat(semantic_form_for(@new_post) do |builder|
722
+ concat(builder.input(:title, :label => true))
723
+ concat(builder.input(:published, :as => :boolean, :label => true))
724
+ end)
725
+ expect(output_buffer).to have_tag('form li label', :text => Regexp.new('^' + @default_localized_label_text))
726
+ end
727
+ end
728
+ end
729
+ end
730
+
731
+ end
732
+
733
+ describe ':hint option' do
734
+
735
+ describe 'when provided' do
736
+
737
+ after do
738
+ Formtastic::FormBuilder.default_hint_class = "inline-hints"
739
+ end
740
+
741
+ it 'should be passed down to the paragraph tag' do
742
+ hint_text = "this is the title of the post"
743
+ concat(semantic_form_for(@new_post) do |builder|
744
+ concat(builder.input(:title, :hint => hint_text))
745
+ end)
746
+ expect(output_buffer).to have_tag("form li p.inline-hints", :text => hint_text)
747
+ end
748
+
749
+ it 'should have a custom hint class defaulted for all forms' do
750
+ hint_text = "this is the title of the post"
751
+ Formtastic::FormBuilder.default_hint_class = "custom-hint-class"
752
+ concat(semantic_form_for(@new_post) do |builder|
753
+ concat(builder.input(:title, :hint => hint_text))
754
+ end)
755
+ expect(output_buffer).to have_tag("form li p.custom-hint-class", :text => hint_text)
756
+ end
757
+ end
758
+
759
+ describe 'when not provided' do
760
+ describe 'when localized hint (I18n) is provided' do
761
+ before do
762
+ @localized_hint_text = "This is the localized hint."
763
+ @default_localized_hint_text = "This is the default localized hint."
764
+ ::I18n.backend.store_translations :en,
765
+ :formtastic => {
766
+ :hints => {
767
+ :title => @default_localized_hint_text,
768
+ }
769
+ }
770
+ end
771
+
772
+ after do
773
+ ::I18n.backend.reload!
774
+ end
775
+
776
+ describe 'when provided value (hint value) is set to TRUE' do
777
+ it 'should render a hint paragraph containing a localized hint (I18n)' do
778
+ with_config :i18n_lookups_by_default, false do
779
+ ::I18n.backend.store_translations :en,
780
+ :formtastic => {
781
+ :hints => {
782
+ :post => {
783
+ :title => @localized_hint_text
784
+ }
785
+ }
786
+ }
787
+ concat(semantic_form_for(@new_post) do |builder|
788
+ concat(builder.input(:title, :hint => true))
789
+ end)
790
+ expect(output_buffer).to have_tag('form li p.inline-hints', :text => @localized_hint_text)
791
+ end
792
+ end
793
+
794
+ it 'should render a hint paragraph containing an optional localized hint (I18n) if first is not set' do
795
+ with_config :i18n_lookups_by_default, false do
796
+ concat(semantic_form_for(@new_post) do |builder|
797
+ concat(builder.input(:title, :hint => true))
798
+ end)
799
+ expect(output_buffer).to have_tag('form li p.inline-hints', :text => @default_localized_hint_text)
800
+ end
801
+ end
802
+ end
803
+
804
+ describe 'when provided value (label value) is set to FALSE' do
805
+ it 'should not render a hint paragraph' do
806
+ with_config :i18n_lookups_by_default, false do
807
+ concat(semantic_form_for(@new_post) do |builder|
808
+ concat(builder.input(:title, :hint => false))
809
+ end)
810
+ expect(output_buffer).not_to have_tag('form li p.inline-hints', :text => @localized_hint_text)
811
+ end
812
+ end
813
+ end
814
+ end
815
+
816
+ describe 'when localized hint (I18n) is a model with attribute hints' do
817
+ it "should see the provided hash as a blank entry" do
818
+ with_config :i18n_lookups_by_default, false do
819
+ ::I18n.backend.store_translations :en,
820
+ :formtastic => {
821
+ :hints => {
822
+ :title => { # movie title
823
+ :summary => @localized_hint_text # summary of movie
824
+ }
825
+ }
826
+ }
827
+ semantic_form_for(@new_post) do |builder|
828
+ concat(builder.input(:title, :hint => true))
829
+ end
830
+ expect(output_buffer).not_to have_tag('form li p.inline-hints', :text => @localized_hint_text)
831
+ end
832
+ end
833
+ end
834
+
835
+ describe 'when localized hint (I18n) is not provided' do
836
+ it 'should not render a hint paragraph' do
837
+ with_config :i18n_lookups_by_default, false do
838
+ concat(semantic_form_for(@new_post) do |builder|
839
+ concat(builder.input(:title))
840
+ end)
841
+ expect(output_buffer).not_to have_tag('form li p.inline-hints')
842
+ end
843
+ end
844
+ end
845
+ end
846
+
847
+ end
848
+
849
+ describe ':wrapper_html option' do
850
+
851
+ describe 'when provided' do
852
+ it 'should be passed down to the li tag' do
853
+ concat(semantic_form_for(@new_post) do |builder|
854
+ concat(builder.input(:title, :wrapper_html => {:id => :another_id}))
855
+ end)
856
+ expect(output_buffer).to have_tag("form li#another_id")
857
+ end
858
+
859
+ it 'should append given classes to li default classes' do
860
+ concat(semantic_form_for(@new_post) do |builder|
861
+ concat(builder.input(:title, :wrapper_html => {:class => :another_class}, :required => true))
862
+ end)
863
+ expect(output_buffer).to have_tag("form li.string")
864
+ expect(output_buffer).to have_tag("form li.required")
865
+ expect(output_buffer).to have_tag("form li.another_class")
866
+ end
867
+
868
+ it 'should allow classes to be an array' do
869
+ concat(semantic_form_for(@new_post) do |builder|
870
+ concat(builder.input(:title, :wrapper_html => {:class => [ :my_class, :another_class ]}))
871
+ end)
872
+ expect(output_buffer).to have_tag("form li.string")
873
+ expect(output_buffer).to have_tag("form li.my_class")
874
+ expect(output_buffer).to have_tag("form li.another_class")
875
+ end
876
+
877
+ describe 'when nil' do
878
+ it 'should not put an id attribute on the div tag' do
879
+ concat(semantic_form_for(@new_post) do |builder|
880
+ concat(builder.input(:title, :wrapper_html => {:id => nil}))
881
+ end)
882
+ expect(output_buffer).to have_tag('form li:not([id])')
883
+ end
884
+ end
885
+ end
886
+
887
+ describe 'when not provided' do
888
+ it 'should use default id and class' do
889
+ concat(semantic_form_for(@new_post) do |builder|
890
+ concat(builder.input(:title))
891
+ end)
892
+ expect(output_buffer).to have_tag("form li#post_title_input")
893
+ expect(output_buffer).to have_tag("form li.string")
894
+ end
895
+ end
896
+
897
+ end
898
+
899
+ describe ':collection option' do
900
+
901
+ it "should be required on polymorphic associations" do
902
+ allow(@new_post).to receive(:commentable)
903
+ allow(@new_post.class).to receive(:reflections).and_return({
904
+ :commentable => double('macro_reflection', :options => { :polymorphic => true }, :macro => :belongs_to)
905
+ })
906
+ allow(@new_post).to receive(:column_for_attribute).with(:commentable).and_return(
907
+ double('column', :type => :integer)
908
+ )
909
+ allow(@new_post.class).to receive(:reflect_on_association).with(:commentable).and_return(
910
+ double('reflection', :macro => :belongs_to, :options => { :polymorphic => true })
911
+ )
912
+ expect {
913
+ concat(semantic_form_for(@new_post) do |builder|
914
+ concat(builder.inputs do
915
+ concat(builder.input :commentable)
916
+ end)
917
+ end)
918
+ }.to raise_error(Formtastic::PolymorphicInputWithoutCollectionError)
919
+ end
920
+
921
+ end
922
+
923
+ end
924
+
925
+ describe 'options re-use' do
926
+
927
+ it 'should retain :as option when re-using the same options hash' do
928
+ my_options = { :as => :string }
929
+ output = ''
930
+
931
+ concat(semantic_form_for(@new_post) do |builder|
932
+ concat(builder.input(:title, my_options))
933
+ concat(builder.input(:publish_at, my_options))
934
+ end)
935
+ expect(output_buffer).to have_tag 'li.string', :count => 2
936
+ end
937
+ end
938
+
939
+ describe 'instantiating an input class' do
940
+ context 'when a class does not exist' do
941
+ it "should raise an error" do
942
+ expect {
943
+ concat(semantic_form_for(@new_post) do |builder|
944
+ builder.input(:title, :as => :non_existant)
945
+ end)
946
+ }.to raise_error(Formtastic::UnknownInputError)
947
+ end
948
+ end
949
+
950
+ context 'when a customized top-level class does not exist' do
951
+
952
+ it 'should instantiate the Formtastic input' do
953
+ input = double('input', :to_html => 'some HTML')
954
+ expect(Formtastic::Inputs::StringInput).to receive(:new).and_return(input)
955
+ concat(semantic_form_for(@new_post) do |builder|
956
+ builder.input(:title, :as => :string)
957
+ end)
958
+ end
959
+
960
+ end
961
+
962
+ describe 'when a top-level input class exists' do
963
+ it "should instantiate the top-level input instead of the Formtastic one" do
964
+ class ::StringInput < Formtastic::Inputs::StringInput
965
+ end
966
+
967
+ input = double('input', :to_html => 'some HTML')
968
+ expect(Formtastic::Inputs::StringInput).not_to receive(:new)
969
+ expect(::StringInput).to receive(:new).and_return(input)
970
+
971
+ concat(semantic_form_for(@new_post) do |builder|
972
+ builder.input(:title, :as => :string)
973
+ end)
974
+ end
975
+ end
976
+
977
+
978
+ end
6
979
  end