formtastic 3.1.3 → 5.0.0

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