simple_form 3.5.0 → 4.0.0

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +203 -31
  4. data/lib/generators/simple_form/install_generator.rb +1 -0
  5. data/lib/generators/simple_form/templates/README +3 -3
  6. data/lib/generators/simple_form/templates/_form.html.erb +2 -0
  7. data/lib/generators/simple_form/templates/_form.html.haml +2 -0
  8. data/lib/generators/simple_form/templates/_form.html.slim +1 -0
  9. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +15 -2
  10. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +358 -73
  11. data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +18 -6
  12. data/lib/simple_form/action_view_extensions/builder.rb +1 -0
  13. data/lib/simple_form/action_view_extensions/form_helper.rb +1 -0
  14. data/lib/simple_form/components/errors.rb +10 -1
  15. data/lib/simple_form/components/hints.rb +1 -0
  16. data/lib/simple_form/components/html5.rb +1 -0
  17. data/lib/simple_form/components/label_input.rb +2 -1
  18. data/lib/simple_form/components/labels.rb +11 -4
  19. data/lib/simple_form/components/maxlength.rb +4 -13
  20. data/lib/simple_form/components/min_max.rb +1 -0
  21. data/lib/simple_form/components/minlength.rb +5 -14
  22. data/lib/simple_form/components/pattern.rb +1 -0
  23. data/lib/simple_form/components/placeholders.rb +2 -1
  24. data/lib/simple_form/components/readonly.rb +1 -0
  25. data/lib/simple_form/components.rb +1 -0
  26. data/lib/simple_form/error_notification.rb +1 -0
  27. data/lib/simple_form/form_builder.rb +74 -19
  28. data/lib/simple_form/helpers/autofocus.rb +1 -0
  29. data/lib/simple_form/helpers/disabled.rb +1 -0
  30. data/lib/simple_form/helpers/readonly.rb +1 -0
  31. data/lib/simple_form/helpers/required.rb +1 -0
  32. data/lib/simple_form/helpers/validators.rb +2 -1
  33. data/lib/simple_form/helpers.rb +1 -0
  34. data/lib/simple_form/i18n_cache.rb +1 -0
  35. data/lib/simple_form/inputs/base.rb +20 -1
  36. data/lib/simple_form/inputs/block_input.rb +1 -0
  37. data/lib/simple_form/inputs/boolean_input.rb +3 -2
  38. data/lib/simple_form/inputs/collection_check_boxes_input.rb +2 -1
  39. data/lib/simple_form/inputs/collection_input.rb +3 -2
  40. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +2 -1
  41. data/lib/simple_form/inputs/collection_select_input.rb +1 -0
  42. data/lib/simple_form/inputs/date_time_input.rb +1 -0
  43. data/lib/simple_form/inputs/file_input.rb +1 -0
  44. data/lib/simple_form/inputs/grouped_collection_select_input.rb +1 -0
  45. data/lib/simple_form/inputs/hidden_input.rb +1 -0
  46. data/lib/simple_form/inputs/numeric_input.rb +1 -0
  47. data/lib/simple_form/inputs/password_input.rb +1 -0
  48. data/lib/simple_form/inputs/priority_input.rb +1 -0
  49. data/lib/simple_form/inputs/range_input.rb +1 -0
  50. data/lib/simple_form/inputs/string_input.rb +1 -0
  51. data/lib/simple_form/inputs/text_input.rb +1 -0
  52. data/lib/simple_form/inputs.rb +1 -0
  53. data/lib/simple_form/map_type.rb +1 -0
  54. data/lib/simple_form/railtie.rb +1 -0
  55. data/lib/simple_form/tags.rb +1 -0
  56. data/lib/simple_form/version.rb +2 -1
  57. data/lib/simple_form/wrappers/builder.rb +1 -0
  58. data/lib/simple_form/wrappers/leaf.rb +2 -1
  59. data/lib/simple_form/wrappers/many.rb +1 -0
  60. data/lib/simple_form/wrappers/root.rb +2 -0
  61. data/lib/simple_form/wrappers/single.rb +2 -1
  62. data/lib/simple_form/wrappers.rb +1 -0
  63. data/lib/simple_form.rb +58 -7
  64. data/test/action_view_extensions/builder_test.rb +6 -5
  65. data/test/action_view_extensions/form_helper_test.rb +3 -2
  66. data/test/components/custom_components_test.rb +62 -0
  67. data/test/components/label_test.rb +33 -4
  68. data/test/form_builder/association_test.rb +27 -2
  69. data/test/form_builder/button_test.rb +1 -0
  70. data/test/form_builder/error_notification_test.rb +1 -0
  71. data/test/form_builder/error_test.rb +6 -0
  72. data/test/form_builder/general_test.rb +44 -3
  73. data/test/form_builder/hint_test.rb +6 -0
  74. data/test/form_builder/input_field_test.rb +27 -1
  75. data/test/form_builder/label_test.rb +9 -3
  76. data/test/form_builder/wrapper_test.rb +24 -4
  77. data/test/generators/simple_form_generator_test.rb +4 -3
  78. data/test/inputs/boolean_input_test.rb +9 -0
  79. data/test/inputs/collection_check_boxes_input_test.rb +30 -14
  80. data/test/inputs/collection_radio_buttons_input_test.rb +40 -24
  81. data/test/inputs/collection_select_input_test.rb +40 -39
  82. data/test/inputs/datetime_input_test.rb +5 -4
  83. data/test/inputs/disabled_test.rb +1 -0
  84. data/test/inputs/discovery_test.rb +1 -0
  85. data/test/inputs/file_input_test.rb +1 -0
  86. data/test/inputs/general_test.rb +3 -2
  87. data/test/inputs/grouped_collection_select_input_test.rb +11 -10
  88. data/test/inputs/hidden_input_test.rb +1 -0
  89. data/test/inputs/numeric_input_test.rb +2 -1
  90. data/test/inputs/priority_input_test.rb +1 -0
  91. data/test/inputs/readonly_test.rb +1 -0
  92. data/test/inputs/required_test.rb +1 -0
  93. data/test/inputs/string_input_test.rb +2 -1
  94. data/test/inputs/text_input_test.rb +1 -0
  95. data/test/simple_form_test.rb +1 -0
  96. data/test/support/discovery_inputs.rb +1 -0
  97. data/test/support/misc_helpers.rb +16 -1
  98. data/test/support/mock_controller.rb +4 -0
  99. data/test/support/models.rb +44 -14
  100. data/test/test_helper.rb +2 -0
  101. metadata +5 -15
data/lib/simple_form.rb CHANGED
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  require 'action_view'
3
+ require 'action_pack'
2
4
  require 'simple_form/action_view_extensions/form_helper'
3
5
  require 'simple_form/action_view_extensions/builder'
4
6
  require 'active_support/core_ext/hash/slice'
@@ -58,11 +60,11 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
58
60
 
59
61
  # Series of attemps to detect a default label method for collection.
60
62
  mattr_accessor :collection_label_methods
61
- @@collection_label_methods = [:to_label, :name, :title, :to_s]
63
+ @@collection_label_methods = %i[to_label name title to_s]
62
64
 
63
65
  # Series of attemps to detect a default value method for collection.
64
66
  mattr_accessor :collection_value_methods
65
- @@collection_value_methods = [:id, :to_s]
67
+ @@collection_value_methods = %i[id to_s]
66
68
 
67
69
  # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
68
70
  mattr_accessor :collection_wrapper_tag
@@ -84,7 +86,7 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
84
86
 
85
87
  # How the label text should be generated altogether with the required text.
86
88
  mattr_accessor :label_text
87
- @@label_text = lambda { |label, required, explicit_label| "#{required} #{label}" }
89
+ @@label_text = ->(label, required, explicit_label) { "#{required} #{label}" }
88
90
 
89
91
  # You can define the class to be used on all labels. Defaults to none.
90
92
  mattr_accessor :label_class
@@ -108,7 +110,7 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
108
110
 
109
111
  # You can define which elements should obtain additional classes.
110
112
  mattr_accessor :generate_additional_classes_for
111
- @@generate_additional_classes_for = [:wrapper, :label, :input]
113
+ @@generate_additional_classes_for = %i[wrapper label input]
112
114
 
113
115
  # Whether attributes are required by default or not.
114
116
  mattr_accessor :required_by_default
@@ -120,7 +122,7 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
120
122
 
121
123
  # Collection of methods to detect if a file type was given.
122
124
  mattr_accessor :file_methods
123
- @@file_methods = [:mounted_as, :file?, :public_filename]
125
+ @@file_methods = %i[mounted_as file? public_filename attached?]
124
126
 
125
127
  # Custom mappings for input types. This should be a hash containing a regexp
126
128
  # to match as key, and the input type that will be used when the field name
@@ -163,7 +165,7 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
163
165
 
164
166
  # Cache SimpleForm inputs discovery.
165
167
  mattr_accessor :cache_discovery
166
- @@cache_discovery = defined?(Rails) && !Rails.env.development?
168
+ @@cache_discovery = defined?(Rails.env) && !Rails.env.development?
167
169
 
168
170
  # Adds a class to each generated button, mostly for compatiblity.
169
171
  mattr_accessor :button_class
@@ -198,6 +200,12 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
198
200
  mattr_accessor :i18n_scope
199
201
  @@i18n_scope = 'simple_form'
200
202
 
203
+ mattr_accessor :input_field_error_class
204
+ @@input_field_error_class = nil
205
+
206
+ mattr_accessor :input_field_valid_class
207
+ @@input_field_valid_class = nil
208
+
201
209
  # Retrieves a given wrapper
202
210
  def self.wrapper(name)
203
211
  @@wrappers[name.to_s] or raise WrapperNotFound, "Couldn't find wrapper with name #{name}"
@@ -227,7 +235,7 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
227
235
  SimpleForm::Wrappers::Root.new(builder.to_a, options)
228
236
  end
229
237
 
230
- wrappers class: :input, hint_class: :field_with_hint, error_class: :field_with_errors do |b|
238
+ wrappers class: :input, hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b|
231
239
  b.use :html5
232
240
 
233
241
  b.use :min_max
@@ -263,6 +271,49 @@ See https://github.com/plataformatec/simple_form/pull/997 for more information.
263
271
  @@configured = true
264
272
  yield self
265
273
  end
274
+
275
+ # Includes a component to be used by Simple Form. Methods defined in a
276
+ # component will be exposed to be used in the wrapper as Simple::Components
277
+ #
278
+ # Examples
279
+ #
280
+ # # The application needs to tell where the components will be.
281
+ # Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
282
+ #
283
+ # # Create a custom component in the path specified above.
284
+ # # lib/components/input_group_component.rb
285
+ # module InputGroupComponent
286
+ # def prepend
287
+ # ...
288
+ # end
289
+ #
290
+ # def append
291
+ # ...
292
+ # end
293
+ # end
294
+ #
295
+ # SimpleForm.setup do |config|
296
+ # # Create a wrapper using the custom component.
297
+ # config.wrappers :input_group, tag: :div, error_class: :error do |b|
298
+ # b.use :label
299
+ # b.optional :prepend
300
+ # b.use :input
301
+ # b.use :append
302
+ # end
303
+ # end
304
+ #
305
+ # # Using the custom component in the form.
306
+ # <%= simple_form_for @blog, wrapper: input_group do |f| %>
307
+ # <%= f.input :title, prepend: true %>
308
+ # <% end %>
309
+ #
310
+ def self.include_component(component)
311
+ if Module === component
312
+ SimpleForm::Inputs::Base.include(component)
313
+ else
314
+ raise TypeError, "SimpleForm.include_component expects a module but got: #{component.class}"
315
+ end
316
+ end
266
317
  end
267
318
 
268
319
  require 'simple_form/railtie' if defined?(Rails)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  class BuilderTest < ActionView::TestCase
@@ -36,7 +37,7 @@ class BuilderTest < ActionView::TestCase
36
37
  end
37
38
 
38
39
  test "collection radio handles camelized collection values for labels correctly" do
39
- with_collection_radio_buttons @user, :active, ['Yes', 'No'], :to_s, :to_s
40
+ with_collection_radio_buttons @user, :active, %w[Yes No], :to_s, :to_s
40
41
 
41
42
  assert_select 'form label.collection_radio_buttons[for=user_active_yes]', 'Yes'
42
43
  assert_select 'form label.collection_radio_buttons[for=user_active_no]', 'No'
@@ -243,7 +244,7 @@ class BuilderTest < ActionView::TestCase
243
244
 
244
245
  test "collection radio with block helpers does not leak the template" do
245
246
  with_concat_form_for(@user) do |f|
246
- collection_input = f.collection_radio_buttons :active, [true, false], :to_s, :to_s do |b|
247
+ collection_input = f.collection_radio_buttons :active, [true, false], :to_s, :to_s do |b|
247
248
  b.label(class: b.object) { b.radio_button + b.text }
248
249
  end
249
250
  concat collection_input
@@ -283,7 +284,7 @@ class BuilderTest < ActionView::TestCase
283
284
  end
284
285
 
285
286
  test "collection check box handles camelized collection values for labels correctly" do
286
- with_collection_check_boxes @user, :active, ['Yes', 'No'], :to_s, :to_s
287
+ with_collection_check_boxes @user, :active, %w[Yes No], :to_s, :to_s
287
288
 
288
289
  assert_select 'form label.collection_check_boxes[for=user_active_yes]', 'Yes'
289
290
  assert_select 'form label.collection_check_boxes[for=user_active_no]', 'No'
@@ -317,7 +318,7 @@ class BuilderTest < ActionView::TestCase
317
318
 
318
319
  test "collection check boxes accepts selected string values as :checked option" do
319
320
  collection = (1..3).map { |i| [i, "Category #{i}"] }
320
- with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: ['1', '3']
321
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: %w[1 3]
321
322
 
322
323
  assert_select 'input[type=checkbox][value="1"][checked=checked]'
323
324
  assert_select 'input[type=checkbox][value="3"][checked=checked]'
@@ -541,7 +542,7 @@ class BuilderTest < ActionView::TestCase
541
542
 
542
543
  test "collection check boxes with block helpers does not leak the template" do
543
544
  with_concat_form_for(@user) do |f|
544
- collection_input = f.collection_check_boxes :active, [true, false], :to_s, :to_s do |b|
545
+ collection_input = f.collection_check_boxes :active, [true, false], :to_s, :to_s do |b|
545
546
  b.label(class: b.object) { b.check_box + b.text }
546
547
  end
547
548
  concat collection_input
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  class FormHelperTest < ActionView::TestCase
@@ -151,7 +152,7 @@ class FormHelperTest < ActionView::TestCase
151
152
  end
152
153
 
153
154
  test 'SimpleForm for swaps default action view field_error_proc' do
154
- expected_error_proc = lambda {}
155
+ expected_error_proc = -> {}
155
156
  swap SimpleForm, field_error_proc: expected_error_proc do
156
157
  simple_form_for :user do |f|
157
158
  assert_equal expected_error_proc, ::ActionView::Base.field_error_proc
@@ -161,7 +162,7 @@ class FormHelperTest < ActionView::TestCase
161
162
 
162
163
  private
163
164
 
164
- def swap_field_error_proc(expected_error_proc = lambda {})
165
+ def swap_field_error_proc(expected_error_proc = -> {})
165
166
  swap ActionView::Base, field_error_proc: expected_error_proc do
166
167
  yield
167
168
 
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ # Module that represents a custom component.
6
+ module Numbers
7
+ def number(wrapper_options = nil)
8
+ @number ||= options[:number].to_s.html_safe
9
+ end
10
+ end
11
+
12
+ # Module that represents a custom component.
13
+ module InputGroup
14
+ def prepend(wrapper_options = nil)
15
+ span_tag = content_tag(:span, options[:prepend], class: 'input-group-text')
16
+ template.content_tag(:div, span_tag, class: 'input-group-prepend')
17
+ end
18
+
19
+ def append(wrapper_options = nil)
20
+ span_tag = content_tag(:span, options[:append], class: 'input-group-text')
21
+ template.content_tag(:div, span_tag, class: 'input-group-append')
22
+ end
23
+ end
24
+
25
+ class CustomComponentsTest < ActionView::TestCase
26
+ test 'includes the custom components' do
27
+ SimpleForm.include_component Numbers
28
+
29
+ custom_wrapper = SimpleForm.build tag: :div, class: "custom_wrapper" do |b|
30
+ b.use :number, wrap_with: { tag: 'div', class: 'number' }
31
+ end
32
+
33
+ with_form_for @user, :name, number: 1, wrapper: custom_wrapper
34
+
35
+ assert_select 'div.number', text: '1'
36
+ end
37
+
38
+ test 'includes custom components and use it as optional in the wrapper' do
39
+ SimpleForm.include_component InputGroup
40
+
41
+ custom_wrapper = SimpleForm.build tag: :div, class: 'custom_wrapper' do |b|
42
+ b.use :label
43
+ b.optional :prepend
44
+ b.use :input
45
+ b.use :append
46
+ end
47
+
48
+ with_form_for @user, :name, prepend: true, wrapper: custom_wrapper
49
+
50
+ assert_select 'div.input-group-prepend > span.input-group-text'
51
+ assert_select 'div.input-group-append > span.input-group-text'
52
+ end
53
+
54
+ test 'raises a TypeError when the component is not a Module' do
55
+ component = 'MyComponent'
56
+
57
+ exception = assert_raises TypeError do
58
+ SimpleForm.include_component(component)
59
+ end
60
+ assert_equal exception.message, "SimpleForm.include_component expects a module but got: String"
61
+ end
62
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # encoding: UTF-8
2
3
  require 'test_helper'
3
4
 
@@ -177,7 +178,7 @@ class IsolatedLabelTest < ActionView::TestCase
177
178
  end
178
179
 
179
180
  test 'label does not have css class from type when generate_additional_classes_for does not include :label' do
180
- swap SimpleForm, generate_additional_classes_for: [:wrapper, :input] do
181
+ swap SimpleForm, generate_additional_classes_for: %i[wrapper input] do
181
182
  with_label_for @user, :name, :string
182
183
  assert_no_select 'label.string'
183
184
  with_label_for @user, :description, :text
@@ -192,7 +193,7 @@ class IsolatedLabelTest < ActionView::TestCase
192
193
  end
193
194
 
194
195
  test 'label does not generate empty css class' do
195
- swap SimpleForm, generate_additional_classes_for: [:wrapper, :input] do
196
+ swap SimpleForm, generate_additional_classes_for: %i[wrapper input] do
196
197
  with_label_for @user, :name, :string
197
198
  assert_no_select 'label[class]'
198
199
  end
@@ -206,7 +207,7 @@ class IsolatedLabelTest < ActionView::TestCase
206
207
  end
207
208
 
208
209
  test 'label does not obtain required from ActiveModel::Validations when generate_additional_classes_for does not include :label' do
209
- swap SimpleForm, generate_additional_classes_for: [:wrapper, :input] do
210
+ swap SimpleForm, generate_additional_classes_for: %i[wrapper input] do
210
211
  with_label_for @validating_user, :name, :string
211
212
  assert_no_select 'label.required'
212
213
  with_label_for @validating_user, :status, :string
@@ -248,6 +249,15 @@ class IsolatedLabelTest < ActionView::TestCase
248
249
  end
249
250
  end
250
251
 
252
+ test 'label uses custom i18n scope to find required text' do
253
+ store_translations(:en, my_scope: { required: { text: 'Pflichtfeld' } }) do
254
+ swap SimpleForm, i18n_scope: :my_scope do
255
+ with_label_for @user, :name, :string
256
+ assert_select 'form label abbr[title="Pflichtfeld"]', '*'
257
+ end
258
+ end
259
+ end
260
+
251
261
  test 'label uses i18n to find required mark' do
252
262
  store_translations(:en, simple_form: { required: { mark: '*-*' } }) do
253
263
  with_label_for @user, :name, :string
@@ -255,6 +265,15 @@ class IsolatedLabelTest < ActionView::TestCase
255
265
  end
256
266
  end
257
267
 
268
+ test 'label uses custom i18n scope to find required mark' do
269
+ store_translations(:en, my_scope: { required: { mark: '!!' } }) do
270
+ swap SimpleForm, i18n_scope: :my_scope do
271
+ with_label_for @user, :name, :string
272
+ assert_select 'form label abbr', '!!'
273
+ end
274
+ end
275
+ end
276
+
258
277
  test 'label uses i18n to find required string tag' do
259
278
  store_translations(:en, simple_form: { required: { html: '<span class="required" title="requerido">*</span>' } }) do
260
279
  with_label_for @user, :name, :string
@@ -263,6 +282,16 @@ class IsolatedLabelTest < ActionView::TestCase
263
282
  end
264
283
  end
265
284
 
285
+ test 'label uses custom i18n scope to find required string tag' do
286
+ store_translations(:en, my_scope: { required: { html: '<span class="mandatory" title="Pflichtfeld">!!</span>' } }) do
287
+ swap SimpleForm, i18n_scope: :my_scope do
288
+ with_label_for @user, :name, :string
289
+ assert_no_select 'form label abbr'
290
+ assert_select 'form label span.mandatory[title=Pflichtfeld]', '!!'
291
+ end
292
+ end
293
+ end
294
+
266
295
  test 'label allows overwriting input id' do
267
296
  with_label_for @user, :name, :string, input_html: { id: 'my_new_id' }
268
297
  assert_select 'label[for=my_new_id]'
@@ -289,7 +318,7 @@ class IsolatedLabelTest < ActionView::TestCase
289
318
  end
290
319
 
291
320
  test 'label includes for attribute for select collection' do
292
- with_label_for @user, :sex, :select, collection: [:male, :female]
321
+ with_label_for @user, :sex, :select, collection: %i[male female]
293
322
  assert_select 'label[for=user_sex]'
294
323
  end
295
324
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # encoding: UTF-8
2
3
  require 'test_helper'
3
4
 
@@ -14,6 +15,12 @@ class AssociationTest < ActionView::TestCase
14
15
  end
15
16
  end
16
17
 
18
+ test 'builder association works with decorated object responsive to #to_model' do
19
+ assert_nothing_raised do
20
+ with_association_for @decorated_user, :company
21
+ end
22
+ end
23
+
17
24
  test 'builder association with a block calls simple_fields_for' do
18
25
  simple_form_for @user do |f|
19
26
  f.association :posts do |posts_form|
@@ -121,6 +128,15 @@ class AssociationTest < ActionView::TestCase
121
128
  assert_no_select 'form select option[value="2"]'
122
129
  end
123
130
 
131
+ test 'builder allows collection to have a scope with parameter' do
132
+ with_association_for @user, :special_tags
133
+ assert_select 'form select.select#user_special_tag_ids'
134
+ assert_select 'form select[multiple=multiple]'
135
+ assert_select 'form select option[value="1"]', 'Tag 1'
136
+ assert_no_select 'form select option[value="2"]'
137
+ assert_no_select 'form select option[value="3"]'
138
+ end
139
+
124
140
  test 'builder marks the record which already belongs to the user' do
125
141
  @user.company_id = 2
126
142
  with_association_for @user, :company, as: :radio_buttons
@@ -154,6 +170,15 @@ class AssociationTest < ActionView::TestCase
154
170
  end
155
171
  end
156
172
 
173
+ test 'builder does not call where if the given association does not respond to it' do
174
+ with_association_for @user, :friends
175
+ assert_select 'form select.select#user_friend_ids'
176
+ assert_select 'form select[multiple=multiple]'
177
+ assert_select 'form select option[value="1"]', 'Friend 1'
178
+ assert_select 'form select option[value="2"]', 'Friend 2'
179
+ assert_select 'form select option[value="3"]', 'Friend 3'
180
+ end
181
+
157
182
  test 'builder does not call order if the given association does not respond to it' do
158
183
  with_association_for @user, :pictures
159
184
  assert_select 'form select.select#user_picture_ids'
@@ -210,12 +235,12 @@ class AssociationTest < ActionView::TestCase
210
235
  end
211
236
 
212
237
  test 'builder with collection support does not change the options hash' do
213
- options = { as: :check_boxes, collection_wrapper_tag: :ul, item_wrapper_tag: :li}
238
+ options = { as: :check_boxes, collection_wrapper_tag: :ul, item_wrapper_tag: :li }
214
239
  with_association_for @user, :tags, options
215
240
 
216
241
  assert_select 'form ul', count: 1
217
242
  assert_select 'form ul li', count: 3
218
- assert_equal({ as: :check_boxes, collection_wrapper_tag: :ul, item_wrapper_tag: :li},
243
+ assert_equal({ as: :check_boxes, collection_wrapper_tag: :ul, item_wrapper_tag: :li },
219
244
  options)
220
245
  end
221
246
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # encoding: UTF-8
2
3
  require 'test_helper'
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # encoding: UTF-8
2
3
  require 'test_helper'
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  # Tests for f.error and f.full_error
@@ -35,6 +36,11 @@ class ErrorTest < ActionView::TestCase
35
36
  assert_select 'span.error', "cannot be blank"
36
37
  end
37
38
 
39
+ test 'error generates messages with decorated object responsive to #to_model' do
40
+ with_error_for @decorated_user, :name
41
+ assert_select 'span.error', "cannot be blank"
42
+ end
43
+
38
44
  test 'error generates messages for attribute with one error when using first' do
39
45
  swap SimpleForm, error_method: :first do
40
46
  with_error_for @user, :age
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # encoding: UTF-8
2
3
  require 'test_helper'
3
4
 
@@ -30,6 +31,12 @@ class FormBuilderTest < ActionView::TestCase
30
31
  end
31
32
  end
32
33
 
34
+ test 'builder works with decorated object responsive to #to_model' do
35
+ assert_nothing_raised do
36
+ with_form_for @decorated_user, :name
37
+ end
38
+ end
39
+
33
40
  test 'builder input allows a block to configure input' do
34
41
  with_form_for @user, :name do
35
42
  text_field_tag :foo, :bar, id: :cool
@@ -49,7 +56,7 @@ class FormBuilderTest < ActionView::TestCase
49
56
  test 'builder does not override custom input mappings for custom collection' do
50
57
  swap SimpleForm, input_mappings: { /gender$/ => :check_boxes } do
51
58
  with_concat_form_for @user do |f|
52
- f.input :gender, collection: [:male, :female]
59
+ f.input :gender, collection: %i[male female]
53
60
  end
54
61
 
55
62
  assert_no_select 'select option', 'Male'
@@ -58,7 +65,7 @@ class FormBuilderTest < ActionView::TestCase
58
65
  end
59
66
 
60
67
  test 'builder allows to skip input_type class' do
61
- swap SimpleForm, generate_additional_classes_for: [:label, :wrapper] do
68
+ swap SimpleForm, generate_additional_classes_for: %i[label wrapper] do
62
69
  with_form_for @user, :post_count
63
70
  assert_no_select "form input#user_post_count.integer"
64
71
  assert_select "form input#user_post_count"
@@ -135,6 +142,24 @@ class FormBuilderTest < ActionView::TestCase
135
142
  assert_select 'form input#user_description.string'
136
143
  end
137
144
 
145
+ test 'builder generates text areas for hstore columns' do
146
+ with_form_for @user, :hstore
147
+ assert_no_select 'form input#user_hstore.string'
148
+ assert_select 'form textarea#user_hstore.text'
149
+ end
150
+
151
+ test 'builder generates text areas for json columns' do
152
+ with_form_for @user, :json
153
+ assert_no_select 'form input#user_json.string'
154
+ assert_select 'form textarea#user_json.text'
155
+ end
156
+
157
+ test 'builder generates text areas for jsonb columns' do
158
+ with_form_for @user, :jsonb
159
+ assert_no_select 'form input#user_jsonb.string'
160
+ assert_select 'form textarea#user_jsonb.text'
161
+ end
162
+
138
163
  test 'builder generates a checkbox for boolean columns' do
139
164
  with_form_for @user, :active
140
165
  assert_select 'form input[type=checkbox]#user_active.boolean'
@@ -159,6 +184,11 @@ class FormBuilderTest < ActionView::TestCase
159
184
  end
160
185
  end
161
186
 
187
+ test 'builder generates string fields for citext columns' do
188
+ with_form_for @user, :citext
189
+ assert_select 'form input#user_citext.string'
190
+ end
191
+
162
192
  test 'builder generates password fields for columns that matches password' do
163
193
  with_form_for @user, :password
164
194
  assert_select 'form input#user_password.password'
@@ -212,6 +242,16 @@ class FormBuilderTest < ActionView::TestCase
212
242
  test 'builder generates file for file columns' do
213
243
  @user.avatar = MiniTest::Mock.new
214
244
  @user.avatar.expect(:public_filename, true)
245
+ @user.avatar.expect(:!, false)
246
+
247
+ with_form_for @user, :avatar
248
+ assert_select 'form input#user_avatar.file'
249
+ end
250
+
251
+ test 'builder generates file for activestorage entries' do
252
+ @user.avatar = MiniTest::Mock.new
253
+ @user.avatar.expect(:attached?, false)
254
+ @user.avatar.expect(:!, false)
215
255
 
216
256
  with_form_for @user, :avatar
217
257
  assert_select 'form input#user_avatar.file'
@@ -220,6 +260,7 @@ class FormBuilderTest < ActionView::TestCase
220
260
  test 'builder generates file for attributes that are real db columns but have file methods' do
221
261
  @user.home_picture = MiniTest::Mock.new
222
262
  @user.home_picture.expect(:mounted_as, true)
263
+ @user.home_picture.expect(:!, false)
223
264
 
224
265
  with_form_for @user, :home_picture
225
266
  assert_select 'form input#user_home_picture.file'
@@ -382,7 +423,7 @@ class FormBuilderTest < ActionView::TestCase
382
423
  end
383
424
 
384
425
  # DEFAULT OPTIONS
385
- [:input, :input_field].each do |method|
426
+ %i[input input_field].each do |method|
386
427
  test "builder receives a default argument and pass it to the inputs when calling '#{method}'" do
387
428
  with_concat_form_for @user, defaults: { input_html: { class: 'default_class' } } do |f|
388
429
  f.public_send(method, :name)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  # Tests for f.hint
@@ -18,6 +19,11 @@ class HintTest < ActionView::TestCase
18
19
  assert_select 'span.hint', 'Use with care...'
19
20
  end
20
21
 
22
+ test 'hint is generated with decorated object responsive to #to_model' do
23
+ with_hint_for @decorated_user, :name, hint: 'Use with care...'
24
+ assert_select 'span.hint', 'Use with care...'
25
+ end
26
+
21
27
  test 'hint does not modify the options hash' do
22
28
  options = { hint: 'Use with care...' }
23
29
  with_hint_for @user, :name, options
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  # Tests for f.input_field
@@ -112,7 +113,7 @@ class InputFieldTest < ActionView::TestCase
112
113
  end
113
114
 
114
115
  test 'builder collection input_field generates input tag with a clean HTML' do
115
- with_input_field_for @user, :status, collection: ['Open', 'Closed'],
116
+ with_input_field_for @user, :status, collection: %w[Open Closed],
116
117
  class: 'status', label_method: :to_s, value_method: :to_s
117
118
 
118
119
  assert_no_select 'select.status[input_html]'
@@ -172,4 +173,29 @@ class InputFieldTest < ActionView::TestCase
172
173
  assert_select 'input[readonly="readonly"]'
173
174
  end
174
175
  end
176
+
177
+ test 'adds valid class to input_field when it is configured' do
178
+ swap SimpleForm, input_field_valid_class: 'is-valid' do
179
+ @user.instance_eval { undef errors }
180
+ with_input_field_for @user, :name
181
+
182
+ assert_select 'input.string.required.is-valid'
183
+ end
184
+ end
185
+
186
+ test 'adds error class to input_field when it is configured' do
187
+ swap SimpleForm, input_field_error_class: 'is-invalid' do
188
+ with_input_field_for @user, :name
189
+
190
+ assert_select 'input.string.required.is-invalid'
191
+ end
192
+ end
193
+
194
+ test 'does not add validation classes to input_field when it is not configured' do
195
+ swap SimpleForm, input_field_error_class: nil, input_field_valid_class: nil do
196
+ with_input_field_for @user, :name
197
+
198
+ assert_select 'input.string.required'
199
+ end
200
+ end
175
201
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # encoding: UTF-8
2
3
  require 'test_helper'
3
4
 
@@ -13,6 +14,11 @@ class LabelTest < ActionView::TestCase
13
14
  assert_select 'label.string[for=user_name]', /Name/
14
15
  end
15
16
 
17
+ test 'builder generates a label for the attribute with decorated object responsive to #to_model' do
18
+ with_label_for @decorated_user, :name
19
+ assert_select 'label.string[for=user_name]', /Name/
20
+ end
21
+
16
22
  test 'builder generates a label for the boolean attrbiute' do
17
23
  with_label_for @user, :name, as: :boolean
18
24
  assert_select 'label.boolean[for=user_name]', /Name/
@@ -108,21 +114,21 @@ class LabelTest < ActionView::TestCase
108
114
  end
109
115
 
110
116
  test 'builder allows custom formatting when label is explicitly specified' do
111
- swap SimpleForm, label_text: lambda { |l, r, explicit_label| explicit_label ? l : "#{l.titleize}:" } do
117
+ swap SimpleForm, label_text: ->(l, r, explicit_label) { explicit_label ? l : "#{l.titleize}:" } do
112
118
  with_label_for @user, :time_zone, 'What is your home time zone?'
113
119
  assert_select 'label[for=user_time_zone]', 'What is your home time zone?'
114
120
  end
115
121
  end
116
122
 
117
123
  test 'builder allows custom formatting when label is generated' do
118
- swap SimpleForm, label_text: lambda { |l, r, explicit_label| explicit_label ? l : "#{l.titleize}:" } do
124
+ swap SimpleForm, label_text: ->(l, r, explicit_label) { explicit_label ? l : "#{l.titleize}:" } do
119
125
  with_label_for @user, :time_zone
120
126
  assert_select 'label[for=user_time_zone]', 'Time Zone:'
121
127
  end
122
128
  end
123
129
 
124
130
  test 'builder allows label specific `label_text` option' do
125
- with_label_for @user, :time_zone, label_text: lambda { |l, _, _| "#{l.titleize}:" }
131
+ with_label_for @user, :time_zone, label_text: ->(l, _, _) { "#{l.titleize}:" }
126
132
 
127
133
  assert_no_select 'label[label_text]'
128
134
  assert_select 'label[for=user_time_zone]', 'Time Zone:'