diff --git a/CHANGELOG.md b/CHANGELOG.md index bc675d2e..9bd5be39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * Ruby 4.0 support (no changes required) * Support procs on validators for minlength/maxlength, and improve validators logic across the board to match Rails [#1859](https://github.com/heartcombo/simple_form/pull/1859) +* Fix label `for` attribute not matching input `id` when collection contains a nil value [#1840](https://github.com/heartcombo/simple_form/issues/1840) ## 5.4.0 diff --git a/lib/simple_form/tags.rb b/lib/simple_form/tags.rb index 2d779b0d..fed27ba0 100644 --- a/lib/simple_form/tags.rb +++ b/lib/simple_form/tags.rb @@ -26,6 +26,20 @@ def render_collection end.join.html_safe end + # Override Rails' sanitize_attribute_name to avoid generating a trailing + # underscore when the collection value is nil (e.g. "method_" instead of + # "method"), which causes the label's `for` attribute to not match the + # input's `id`. See https://github.com/heartcombo/simple_form/issues/1840 + def sanitize_attribute_name(value) + sanitized = sanitized_value(value) + + if sanitized.empty? + sanitized_method_name.dup + else + "#{sanitized_method_name}_#{sanitized}" + end + end + def wrap_rendered_collection(collection) wrapper_tag = @options[:collection_wrapper_tag] diff --git a/test/inputs/collection_check_boxes_input_test.rb b/test/inputs/collection_check_boxes_input_test.rb index 139ac33f..03d0e274 100644 --- a/test/inputs/collection_check_boxes_input_test.rb +++ b/test/inputs/collection_check_boxes_input_test.rb @@ -314,4 +314,24 @@ class CollectionCheckBoxesInputTest < ActionView::TestCase assert_select 'label.beautiful-label', count: 2 end end + + test 'input check boxes label for attribute matches input id when collection contains nil value' do + with_input_for @user, :name, :check_boxes, collection: [['Yes', true], ['Undefined', nil]] + + assert_select 'input[type=checkbox][value=true]#user_name_true' + assert_select 'label.collection_check_boxes[for=user_name_true]', 'Yes' + assert_select 'input[type=checkbox]#user_name' + assert_select 'label.collection_check_boxes[for=user_name]', 'Undefined' + assert_no_select 'label[for=user_name_]' + end + + test 'input check boxes with nested style label for attribute matches input id when collection contains nil value' do + swap SimpleForm, boolean_style: :nested do + with_input_for @user, :name, :check_boxes, collection: [['Yes', true], ['Undefined', nil]] + + assert_select 'label[for=user_name_true]' + assert_select 'label[for=user_name]' + assert_no_select 'label[for=user_name_]' + end + end end diff --git a/test/inputs/collection_radio_buttons_input_test.rb b/test/inputs/collection_radio_buttons_input_test.rb index 94c5aaaa..2b75fcb6 100644 --- a/test/inputs/collection_radio_buttons_input_test.rb +++ b/test/inputs/collection_radio_buttons_input_test.rb @@ -437,4 +437,24 @@ class CollectionRadioButtonsInputTest < ActionView::TestCase assert_select 'label.beautiful-label', count: 2 end end + + test 'input radio label for attribute matches input id when collection contains nil value' do + with_input_for @user, :name, :radio_buttons, collection: [['Yes', true], ['Undefined', nil]] + + assert_select 'input[type=radio][value=true]#user_name_true' + assert_select 'label.collection_radio_buttons[for=user_name_true]', 'Yes' + assert_select 'input[type=radio]#user_name' + assert_select 'label.collection_radio_buttons[for=user_name]', 'Undefined' + assert_no_select 'label[for=user_name_]' + end + + test 'input radio with nested style label for attribute matches input id when collection contains nil value' do + swap SimpleForm, boolean_style: :nested do + with_input_for @user, :name, :radio_buttons, collection: [['Yes', true], ['Undefined', nil]] + + assert_select 'label[for=user_name_true]' + assert_select 'label[for=user_name]' + assert_no_select 'label[for=user_name_]' + end + end end