formtastic 1.2.4 → 3.1.5

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