formtastic 3.1.6 → 4.0.0.rc1

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