diff --git a/README.md b/README.md index b6b3d637..4c44423b 100644 --- a/README.md +++ b/README.md @@ -1493,38 +1493,38 @@ Generated HTML:
- -
is invalid
+ +
is invalid
Misc
- +
- + -
is invalid
+
is invalid
Preferences
- +
- + -
is invalid
+
is invalid
- -
is invalid
+ +
is invalid
``` @@ -1554,8 +1554,8 @@ Generated HTML: ```html
- - + +
``` @@ -1652,7 +1652,7 @@ Which outputs: ```html
-
Email is invalid
+
Email is invalid
``` @@ -1673,7 +1673,7 @@ Which outputs: ```html
-
is invalid
+
is invalid
``` @@ -1692,7 +1692,7 @@ Which outputs: ```html
-
Email is invalid
+
Email is invalid
``` diff --git a/lib/bootstrap_form/components/labels.rb b/lib/bootstrap_form/components/labels.rb index d8f03655..fe6eec97 100644 --- a/lib/bootstrap_form/components/labels.rb +++ b/lib/bootstrap_form/components/labels.rb @@ -52,6 +52,7 @@ def prepare_label_options(id, name, options, custom_label_col, group_layout) options[:class] = label_classes(name, options, custom_label_col, group_layout) options.delete(:class) if options[:class].none? + options[:id] = aria_feedback_id(name:, id:) if error?(name) && label_errors end end end diff --git a/lib/bootstrap_form/components/validation.rb b/lib/bootstrap_form/components/validation.rb index affba190..48d1dfcb 100644 --- a/lib/bootstrap_form/components/validation.rb +++ b/lib/bootstrap_form/components/validation.rb @@ -61,14 +61,14 @@ def inline_error?(name) error?(name) && inline_errors end - def generate_error(name) + def generate_error(name, id) return unless inline_error?(name) help_text = get_error_messages(name) help_klass = "invalid-feedback" help_tag = :div - content_tag(help_tag, help_text, class: help_klass) + content_tag(help_tag, help_text, class: help_klass, id: aria_feedback_id(id:, name:)) end def get_error_messages(name) @@ -84,6 +84,10 @@ def get_error_messages(name) safe_join(object.errors[name], ", ") end # rubocop:enable Metrics/AbcSize + + def aria_feedback_id(name:, id: nil) + id.present? ? "#{id}_feedback" : field_id(name, :feedback) + end end end end diff --git a/lib/bootstrap_form/form_group.rb b/lib/bootstrap_form/form_group.rb index c244f4b2..aeda4a76 100644 --- a/lib/bootstrap_form/form_group.rb +++ b/lib/bootstrap_form/form_group.rb @@ -23,7 +23,7 @@ def form_group_content_tag(name, field_name, without_field_name, options, html_o html_class = control_specific_class(field_name) html_class = "#{html_class} col-auto g-3" if @layout == :horizontal && options[:skip_inline].blank? tag.div(class: html_class) do - input_with_error(name) do + input_with_error(name, options[:id]) do send(without_field_name, name, options, html_options) end end diff --git a/lib/bootstrap_form/form_group_builder.rb b/lib/bootstrap_form/form_group_builder.rb index 36e7155a..8e4989c6 100644 --- a/lib/bootstrap_form/form_group_builder.rb +++ b/lib/bootstrap_form/form_group_builder.rb @@ -98,7 +98,10 @@ def form_group_css_options(method, html_options, options) # Add control_class; allow it to be overridden by :control_class option control_classes = css_options.delete(:control_class) { control_class } css_options[:class] = safe_join([control_classes, css_options[:class]].compact, " ") - css_options[:class] << " is-invalid" if error?(method) + if error?(method) + css_options[:class] << " is-invalid" + css_options[:aria] = { labelledby: aria_feedback_id(id: options[:id], name: method) } + end css_options[:placeholder] = form_group_placeholder(options, method) if options[:label_as_placeholder] css_options end diff --git a/lib/bootstrap_form/helpers/bootstrap.rb b/lib/bootstrap_form/helpers/bootstrap.rb index 2048c448..1cf39ce4 100644 --- a/lib/bootstrap_form/helpers/bootstrap.rb +++ b/lib/bootstrap_form/helpers/bootstrap.rb @@ -34,7 +34,10 @@ def errors_on(name, options={}) hide_attribute_name = options[:hide_attribute_name] || false custom_class = options[:custom_class] || false - tag.div class: custom_class || "invalid-feedback" do + tag.div( + class: custom_class || "invalid-feedback", + id: aria_feedback_id(id: options[:id], name:) + ) do errors = if hide_attribute_name object.errors[name] else @@ -66,20 +69,21 @@ def custom_control(*args, &) end def prepend_and_append_input(name, options, &) + id = options[:id] options = options.extract!(:prepend, :append, :input_group_class).compact input = capture(&) || ActiveSupport::SafeBuffer.new input = attach_input(options, :prepend) + input + attach_input(options, :append) - input << generate_error(name) + input << generate_error(name, id) options.present? && input = tag.div(input, class: ["input-group", options[:input_group_class]].compact) input end - def input_with_error(name, &) + def input_with_error(name, id, &) input = capture(&) - input << generate_error(name) + input << generate_error(name, id) end def input_group_content(content) diff --git a/lib/bootstrap_form/inputs/base.rb b/lib/bootstrap_form/inputs/base.rb index 94833bd9..759c19be 100644 --- a/lib/bootstrap_form/inputs/base.rb +++ b/lib/bootstrap_form/inputs/base.rb @@ -24,6 +24,9 @@ def bootstrap_field(field_name) def bootstrap_select_group(field_name) define_method(:"#{field_name}_with_bootstrap") do |name, options={}, html_options={}| + # Specifying the id for a select doesn't work. The Rails helpers need to generate + # what they generate, and that includes the ids for each select option. + options.delete(:id) html_options = html_options.reverse_merge(control_class: "form-select") form_group_builder(name, options, html_options) do form_group_content_tag(name, field_name, "#{field_name}_without_bootstrap", options, html_options) diff --git a/lib/bootstrap_form/inputs/check_box.rb b/lib/bootstrap_form/inputs/check_box.rb index fca776d0..840b84ed 100644 --- a/lib/bootstrap_form/inputs/check_box.rb +++ b/lib/bootstrap_form/inputs/check_box.rb @@ -13,7 +13,7 @@ def check_box_with_bootstrap(name, options={}, checked_value="1", unchecked_valu content = tag.div(class: check_box_wrapper_class(options), **options[:wrapper].to_h.except(:class)) do html = check_box_without_bootstrap(name, check_box_options(name, options), checked_value, unchecked_value) html << check_box_label(name, options, checked_value, &block) unless options[:skip_label] - html << generate_error(name) if options[:error_message] + html << generate_error(name, options[:id]) if options[:error_message] html end wrapper(content, options) @@ -41,6 +41,7 @@ def check_box_options(name, options) :inline, :label, :label_class, :label_col, :layout, :skip_label, :switch, :wrapper, :wrapper_class) check_box_options[:class] = check_box_classes(name, options) + check_box_options[:aria] = { labelledby: aria_feedback_id(id: options[:id], name:) } if error?(name) check_box_options.merge!(required_field_options(options, name)) end diff --git a/lib/bootstrap_form/inputs/collection_check_boxes.rb b/lib/bootstrap_form/inputs/collection_check_boxes.rb index 6b18edb7..4c028427 100644 --- a/lib/bootstrap_form/inputs/collection_check_boxes.rb +++ b/lib/bootstrap_form/inputs/collection_check_boxes.rb @@ -9,6 +9,7 @@ module CollectionCheckBoxes included do def collection_check_boxes_with_bootstrap(*args) + args[4]&.delete(:id) html = inputs_collection(*args) do |name, value, options| options[:multiple] = true check_box(name, options, value, nil) diff --git a/lib/bootstrap_form/inputs/collection_radio_buttons.rb b/lib/bootstrap_form/inputs/collection_radio_buttons.rb index be07db35..d6d6d5c5 100644 --- a/lib/bootstrap_form/inputs/collection_radio_buttons.rb +++ b/lib/bootstrap_form/inputs/collection_radio_buttons.rb @@ -9,6 +9,7 @@ module CollectionRadioButtons included do def collection_radio_buttons_with_bootstrap(*args) + args[4]&.delete(:id) inputs_collection(*args) do |name, value, options| radio_button(name, value, options) end diff --git a/lib/bootstrap_form/inputs/inputs_collection.rb b/lib/bootstrap_form/inputs/inputs_collection.rb index 2b5dc808..9d742255 100644 --- a/lib/bootstrap_form/inputs/inputs_collection.rb +++ b/lib/bootstrap_form/inputs/inputs_collection.rb @@ -89,7 +89,7 @@ def field_group(name, options, &) :add_control_col_class, :append, :control_col, :floating, :help, :icon, :id, :input_group_class, :label, :label_col, :layout, :prepend ), - aria: { labelledby: options[:id] || default_id(name) }, + aria: { labelledby: group_label_div_id(id: options[:id], name:) }, role: :group ) do group_label_div = generate_group_label_div(name, options) @@ -100,15 +100,14 @@ def field_group(name, options, &) def generate_group_label_div(name, options) group_label_div_class = options.dig(:label, :class) || "form-label" - id = options[:id] || default_id(name) tag.div( **{ class: group_label_div_class }.compact, - id: + id: group_label_div_id(id: options[:id], name:) ) { label_text(name, options.dig(:label, :text)) } end - def default_id(name) = raw("#{object_name}_#{name}") # rubocop:disable Rails/OutputSafety + def group_label_div_id(id:, name:) = id || field_id(name) end end end diff --git a/lib/bootstrap_form/inputs/radio_button.rb b/lib/bootstrap_form/inputs/radio_button.rb index d78e0e11..ad7ddbe5 100644 --- a/lib/bootstrap_form/inputs/radio_button.rb +++ b/lib/bootstrap_form/inputs/radio_button.rb @@ -14,7 +14,7 @@ def radio_button_with_bootstrap(name, value, *args) tag.div(**wrapper_attributes) do html = radio_button_without_bootstrap(name, value, radio_button_options(name, options)) html << radio_button_label(name, value, options) unless options[:skip_label] - html << generate_error(name) if options[:error_message] + html << generate_error(name, options[:id]) if options[:error_message] html end end @@ -28,6 +28,7 @@ def radio_button_options(name, options) radio_button_options = options.except(:class, :label, :label_class, :error_message, :help, :inline, :hide_label, :skip_label, :wrapper, :wrapper_class) radio_button_options[:class] = radio_button_classes(name, options) + radio_button_options[:aria] = { labelledby: aria_feedback_id(id: options[:id], name:) } if error?(name) radio_button_options.merge!(required_field_options(options, name)) end diff --git a/lib/bootstrap_form/inputs/time_zone_select.rb b/lib/bootstrap_form/inputs/time_zone_select.rb index 555c2902..e174ce9b 100644 --- a/lib/bootstrap_form/inputs/time_zone_select.rb +++ b/lib/bootstrap_form/inputs/time_zone_select.rb @@ -10,7 +10,7 @@ module TimeZoneSelect def time_zone_select_with_bootstrap(method, priority_zones=nil, options={}, html_options={}) html_options = html_options.reverse_merge(control_class: "form-select") form_group_builder(method, options, html_options) do - input_with_error(method) do + input_with_error(method, options[:id]) do time_zone_select_without_bootstrap(method, priority_zones, options, html_options) end end diff --git a/test/bootstrap_checkbox_test.rb b/test/bootstrap_checkbox_test.rb index 121ba276..95692b82 100644 --- a/test/bootstrap_checkbox_test.rb +++ b/test/bootstrap_checkbox_test.rb @@ -195,11 +195,11 @@ class BootstrapCheckboxTest < ActionView::TestCase
- + -
You must accept the terms.
+
You must accept the terms.
HTML @@ -209,6 +209,26 @@ class BootstrapCheckboxTest < ActionView::TestCase assert_equivalent_html expected, actual end + test "check_box renders error when asked with specified id:" do + @user.errors.add(:terms, "You must accept the terms.") + expected = <<~HTML +
+
+ + + +
You must accept the terms.
+
+
+ HTML + actual = bootstrap_form_for(@user) do |f| + f.check_box(:terms, label: "I agree to the terms", error_message: true, id: "custom-id") + end + assert_equivalent_html expected, actual + end + test "check box with custom wrapper class" do expected = <<~HTML
diff --git a/test/bootstrap_collection_checkboxes_test.rb b/test/bootstrap_collection_checkboxes_test.rb index 667b3008..7bf80353 100644 --- a/test/bootstrap_collection_checkboxes_test.rb +++ b/test/bootstrap_collection_checkboxes_test.rb @@ -368,13 +368,13 @@ class BootstrapCollectionCheckboxesTest < ActionView::TestCase
Misc
- +
- + -
a box must be checked
+
a box must be checked
@@ -387,6 +387,35 @@ class BootstrapCollectionCheckboxesTest < ActionView::TestCase assert_equivalent_html expected, actual end + test "collection_check_boxes renders error after last check box with specified id:" do + collection = [Address.new(id: 1, street: "Foo"), Address.new(id: 2, street: "Bar")] + @user.errors.add(:misc, "a box must be checked") + + expected = <<~HTML +
+ +
+
Misc
+
+ + +
+
+ + +
a box must be checked
+
+
+
+ HTML + + actual = bootstrap_form_for(@user) do |f| + f.collection_check_boxes(:misc, collection, :id, :street, { id: "custom-id" }) + end + + assert_equivalent_html expected, actual + end + test "collection_check_boxes renders data attributes" do collection = [ ["1", "Foo", { "data-city": "east" }], @@ -418,13 +447,13 @@ class BootstrapCollectionCheckboxesTest < ActionView::TestCase
Misc
- +
- + -
error for test
+
error for test
@@ -435,6 +464,33 @@ class BootstrapCollectionCheckboxesTest < ActionView::TestCase end assert_equivalent_html expected, actual end + + test "collection_check_boxes renders multiple check boxes with error correctly with specified id:" do + @user.errors.add(:misc, "error for test") + collection = [Address.new(id: 1, street: "Foo"), Address.new(id: 2, street: "Bar")] + expected = <<~HTML +
+ +
+
Misc
+
+ + +
+
+ + +
error for test
+
+
+
+ HTML + + actual = bootstrap_form_for(@user) do |f| + f.collection_check_boxes(:misc, collection, :id, :street, checked: collection, id: "custom-id") + end + assert_equivalent_html expected, actual + end end class BootstrapLegacyCollectionCheckboxesTest < ActionView::TestCase @@ -803,13 +859,13 @@ class BootstrapLegacyCollectionCheckboxesTest < ActionView::TestCase
- +
- + -
a box must be checked
+
a box must be checked
@@ -822,6 +878,35 @@ class BootstrapLegacyCollectionCheckboxesTest < ActionView::TestCase assert_equivalent_html expected, actual end + test "collection_check_boxes renders error after last check box with specified id:" do + collection = [Address.new(id: 1, street: "Foo"), Address.new(id: 2, street: "Bar")] + @user.errors.add(:misc, "a box must be checked") + + expected = <<~HTML +
+ +
+ +
+ + +
+
+ + +
a box must be checked
+
+
+
+ HTML + + actual = bootstrap_form_for(@user) do |f| + f.collection_check_boxes(:misc, collection, :id, :street, { id: "custom-id" }) + end + + assert_equivalent_html expected, actual + end + test "collection_check_boxes renders data attributes" do collection = [ ["1", "Foo", { "data-city": "east" }], @@ -853,13 +938,13 @@ class BootstrapLegacyCollectionCheckboxesTest < ActionView::TestCase
- +
- + -
error for test
+
error for test
@@ -870,4 +955,31 @@ class BootstrapLegacyCollectionCheckboxesTest < ActionView::TestCase end assert_equivalent_html expected, actual end + + test "collection_check_boxes renders multiple check boxes with error correctly with specified id:" do + @user.errors.add(:misc, "error for test") + collection = [Address.new(id: 1, street: "Foo"), Address.new(id: 2, street: "Bar")] + expected = <<~HTML +
+ +
+ +
+ + +
+
+ + +
error for test
+
+
+
+ HTML + + actual = bootstrap_form_for(@user) do |f| + f.collection_check_boxes(:misc, collection, :id, :street, checked: collection, id: "custom-id") + end + assert_equivalent_html expected, actual + end end diff --git a/test/bootstrap_collection_radio_buttons_test.rb b/test/bootstrap_collection_radio_buttons_test.rb index 2c9344fe..a57603a5 100644 --- a/test/bootstrap_collection_radio_buttons_test.rb +++ b/test/bootstrap_collection_radio_buttons_test.rb @@ -61,13 +61,13 @@ class BootstrapCollectionRadioButtonsTest < ActionView::TestCase
Misc
- +
- + -
error for test
+
error for test
@@ -79,6 +79,32 @@ class BootstrapCollectionRadioButtonsTest < ActionView::TestCase assert_equivalent_html expected, actual end + test "collection_radio_buttons renders multiple radios with error correctly with specified id:" do + @user.errors.add(:misc, "error for test") + collection = [Address.new(id: 1, street: "Foo"), Address.new(id: 2, street: "Bar")] + expected = <<~HTML +
+
+
Misc
+
+ + +
+
+ + +
error for test
+
+
+
+ HTML + + actual = bootstrap_form_for(@user) do |f| + f.collection_radio_buttons(:misc, collection, :id, :street, { id: "custom-id" }) + end + assert_equivalent_html expected, actual + end + test "collection_radio_buttons renders inline radios correctly" do collection = [Address.new(id: 1, street: "Foo"), Address.new(id: 2, street: "Bar")] expected = <<~HTML @@ -327,13 +353,13 @@ class BootstrapCLegacyollectionRadioButtonsTest < ActionView::TestCase
- +
- + -
error for test
+
error for test
@@ -345,6 +371,32 @@ class BootstrapCLegacyollectionRadioButtonsTest < ActionView::TestCase assert_equivalent_html expected, actual end + test "collection_radio_buttons renders multiple radios with error correctly with specified id:" do + @user.errors.add(:misc, "error for test") + collection = [Address.new(id: 1, street: "Foo"), Address.new(id: 2, street: "Bar")] + expected = <<~HTML +
+
+ +
+ + +
+
+ + +
error for test
+
+
+
+ HTML + + actual = bootstrap_form_for(@user) do |f| + f.collection_radio_buttons(:misc, collection, :id, :street, { id: "custom-id" }) + end + assert_equivalent_html expected, actual + end + test "collection_radio_buttons renders inline radios correctly" do collection = [Address.new(id: 1, street: "Foo"), Address.new(id: 2, street: "Bar")] expected = <<~HTML diff --git a/test/bootstrap_fields_for_test.rb b/test/bootstrap_fields_for_test.rb index eedcef36..f79fa2a1 100644 --- a/test/bootstrap_fields_for_test.rb +++ b/test/bootstrap_fields_for_test.rb @@ -41,7 +41,7 @@ class BootstrapFieldsForTest < ActionView::TestCase
- +
diff --git a/test/bootstrap_fields_test.rb b/test/bootstrap_fields_test.rb index e456c992..efce7615 100644 --- a/test/bootstrap_fields_test.rb +++ b/test/bootstrap_fields_test.rb @@ -93,14 +93,28 @@ class BootstrapFieldsTest < ActionView::TestCase
- -
error for test
+ +
error for test
HTML assert_equivalent_html expected, bootstrap_form_for(@user) { |f| f.file_field(:misc) } end + test "file fields are wrapped correctly with error with specified id:" do + @user.errors.add(:misc, "error for test") + expected = <<~HTML +
+
+ + +
error for test
+
+
+ HTML + assert_equivalent_html expected, bootstrap_form_for(@user) { |f| f.file_field(:misc, id: "custom-id") } + end + test "errors are correctly displayed for belongs_to association fields" do @address.valid? @@ -108,14 +122,29 @@ class BootstrapFieldsTest < ActionView::TestCase
- -
must exist
+ +
must exist
HTML assert_equivalent_html expected, bootstrap_form_for(@address, url: users_path) { |f| f.text_field(:user_id) } end + test "errors are correctly displayed for belongs_to association fields with specified id:" do + @address.valid? + + expected = <<~HTML +
+
+ + +
must exist
+
+
+ HTML + assert_equivalent_html expected, bootstrap_form_for(@address, url: users_path) { |f| f.text_field(:user_id, id: "custom-id") } + end + test "hidden fields are supported" do expected = <<~HTML diff --git a/test/bootstrap_form_group_test.rb b/test/bootstrap_form_group_test.rb index 6356afea..cf526b58 100644 --- a/test/bootstrap_form_group_test.rb +++ b/test/bootstrap_form_group_test.rb @@ -187,9 +187,9 @@ class BootstrapFormGroupTest < ActionView::TestCase
$ - + .00 -
can't be blank, is too short (minimum is 5 characters) +
can't be blank, is too short (minimum is 5 characters)
@@ -434,28 +434,6 @@ class BootstrapFormGroupTest < ActionView::TestCase assert_equivalent_html expected, output end - test 'upgrade doc for form_group renders the "error" class and message correctly when object is invalid' do - @user.email = nil - assert @user.invalid? - - output = @builder.form_group :email do - html = '

Bar

'.html_safe - unless @user.errors[:email].empty? - html << tag.div(@user.errors[:email].join(", "), class: "invalid-feedback", - style: "display: block;") - end - html - end - - expected = <<~HTML -
-

Bar

-
can't be blank, is too short (minimum is 5 characters)
-
- HTML - assert_equivalent_html expected, output - end - test "upgrade doc for form_group renders check box correctly when object is invalid" do @user.errors.add(:misc, "Must select one.") @@ -471,17 +449,17 @@ class BootstrapFormGroupTest < ActionView::TestCase
- +
- +
- + -
Must select one.
+
Must select one.
@@ -509,9 +487,9 @@ class BootstrapFormGroupTest < ActionView::TestCase
- +
-
can't be blank, is too short (minimum is 5 characters)
+
can't be blank, is too short (minimum is 5 characters)
HTML output = @builder.email_field(:email, wrapper_class: "none-margin") @@ -530,8 +508,8 @@ class BootstrapFormGroupTest < ActionView::TestCase
- -
can't be blank, is too short (minimum is 5 characters)
+ +
can't be blank, is too short (minimum is 5 characters)
This is required
diff --git a/test/bootstrap_form_test.rb b/test/bootstrap_form_test.rb index 22050769..4cbb7fb5 100644 --- a/test/bootstrap_form_test.rb +++ b/test/bootstrap_form_test.rb @@ -458,14 +458,29 @@ def warn(message, ...) expected = <<~HTML
- - + +
HTML assert_equivalent_html expected, bootstrap_form_for(@user, label_errors: true) { |f| f.text_field :email } end + test "errors display correctly and inline_errors are turned off by default when label_errors is true with specified id:" do + @user.email = nil + assert @user.invalid? + + expected = <<~HTML +
+
+ + +
+
+ HTML + assert_equivalent_html expected, bootstrap_form_for(@user, label_errors: true) { |f| f.text_field :email, id: "custom-id" } + end + test "errors display correctly and inline_errors can also be on when label_errors is true" do @user.email = nil assert @user.invalid? @@ -473,15 +488,33 @@ def warn(message, ...) expected = <<~HTML
- - -
can't be blank, is too short (minimum is 5 characters) + + +
can't be blank, is too short (minimum is 5 characters)
HTML assert_equivalent_html expected, bootstrap_form_for(@user, label_errors: true, inline_errors: true) { |f| f.text_field :email } end + test "errors display correctly and inline_errors can also be on when label_errors is true with specified id:" do + @user.email = nil + assert @user.invalid? + + expected = <<~HTML +
+
+ + +
can't be blank, is too short (minimum is 5 characters) +
+ + HTML + assert_equivalent_html expected, bootstrap_form_for(@user, label_errors: true, inline_errors: true) { |f| + f.text_field :email, id: "custom-id" + } + end + test "label error messages use humanized attribute names" do I18n.backend.store_translations(:en, activerecord: { attributes: { user: { email: "Your e-mail address" } } }) @@ -491,9 +524,9 @@ def warn(message, ...) expected = <<~HTML
- - -
can't be blank, is too short (minimum is 5 characters)
+ + +
can't be blank, is too short (minimum is 5 characters)
HTML @@ -502,6 +535,28 @@ def warn(message, ...) I18n.backend.store_translations(:en, activerecord: { attributes: { user: { email: nil } } }) end + test "label error messages use humanized attribute names with specified id:" do + I18n.backend.store_translations(:en, activerecord: { attributes: { user: { email: "Your e-mail address" } } }) + + @user.email = nil + assert @user.invalid? + + expected = <<~HTML +
+
+ + +
can't be blank, is too short (minimum is 5 characters)
+
+
+ HTML + assert_equivalent_html expected, bootstrap_form_for(@user, label_errors: true, inline_errors: true) { |f| + f.text_field :email, id: "custom-id" + } + ensure + I18n.backend.store_translations(:en, activerecord: { attributes: { user: { email: nil } } }) + end + test "alert message is wrapped correctly" do @user.email = nil assert @user.invalid? @@ -624,7 +679,7 @@ def warn(message, ...) assert @user.invalid? expected = <<~HTML -
Email can't be blank, Email is too short (minimum is 5 characters)
+
Email can't be blank, Email is too short (minimum is 5 characters)
HTML assert_equivalent_html expected, @builder.errors_on(:email) end @@ -717,8 +772,8 @@ def warn(message, ...)
- -
can't be blank, is too short (minimum is 5 characters)
+ +
can't be blank, is too short (minimum is 5 characters)
This is required
@@ -741,9 +796,9 @@ def warn(message, ...)
- +
-
can't be blank, is too short (minimum is 5 characters)
+
can't be blank, is too short (minimum is 5 characters)
This is required
@@ -763,7 +818,7 @@ def warn(message, ...)
- + This is required
@@ -807,7 +862,9 @@ def warn(message, ...) @user.email = nil assert @user.invalid? - expected = '
can\'t be blank, is too short (minimum is 5 characters)
' + expected = <<~HTML +
can't be blank, is too short (minimum is 5 characters)
+ HTML assert_equivalent_html expected, @builder.errors_on(:email, hide_attribute_name: true) end @@ -816,7 +873,11 @@ def warn(message, ...) @user.email = nil assert @user.invalid? - expected = '
Email can\'t be blank, Email is too short (minimum is 5 characters)
' + expected = <<~HTML +
+ Email can't be blank, Email is too short (minimum is 5 characters) +
+ HTML assert_equivalent_html expected, @builder.errors_on(:email, custom_class: "custom-error-class") end @@ -1111,14 +1172,29 @@ def warn(message, ...) expected = <<~HTML
- - + +
HTML assert_equivalent_html expected, bootstrap_form_for(@user, label_errors: true) { |f| f.text_field :email } end + test "errors display correctly and inline_errors are turned off by default when label_errors is true with specified id:" do + @user.email = nil + assert @user.invalid? + + expected = <<~HTML +
+
+ + +
+
+ HTML + assert_equivalent_html expected, bootstrap_form_for(@user, label_errors: true) { |f| f.text_field :email, id: "custom-id" } + end + test "errors display correctly and inline_errors can also be on when label_errors is true" do @user.email = nil assert @user.invalid? @@ -1126,15 +1202,33 @@ def warn(message, ...) expected = <<~HTML
- - -
can't be blank, is too short (minimum is 5 characters) + + +
can't be blank, is too short (minimum is 5 characters)
HTML assert_equivalent_html expected, bootstrap_form_for(@user, label_errors: true, inline_errors: true) { |f| f.text_field :email } end + test "errors display correctly and inline_errors can also be on when label_errors is true with specified id:" do + @user.email = nil + assert @user.invalid? + + expected = <<~HTML +
+
+ + +
can't be blank, is too short (minimum is 5 characters) +
+ + HTML + assert_equivalent_html expected, bootstrap_form_for(@user, label_errors: true, inline_errors: true) { |f| + f.text_field :email, id: "custom-id" + } + end + test "label error messages use humanized attribute names" do I18n.backend.store_translations(:en, activerecord: { attributes: { user: { email: "Your e-mail address" } } }) @@ -1144,9 +1238,9 @@ def warn(message, ...) expected = <<~HTML
- - -
can't be blank, is too short (minimum is 5 characters)
+ + +
can't be blank, is too short (minimum is 5 characters)
HTML @@ -1155,6 +1249,28 @@ def warn(message, ...) I18n.backend.store_translations(:en, activerecord: { attributes: { user: { email: nil } } }) end + test "label error messages use humanized attribute names with specified id:" do + I18n.backend.store_translations(:en, activerecord: { attributes: { user: { email: "Your e-mail address" } } }) + + @user.email = nil + assert @user.invalid? + + expected = <<~HTML +
+
+ + +
can't be blank, is too short (minimum is 5 characters)
+
+
+ HTML + assert_equivalent_html expected, bootstrap_form_for(@user, label_errors: true, inline_errors: true) { |f| + f.text_field :email, id: "custom-id" + } + ensure + I18n.backend.store_translations(:en, activerecord: { attributes: { user: { email: nil } } }) + end + test "alert message is wrapped correctly" do @user.email = nil assert @user.invalid? @@ -1277,7 +1393,7 @@ def warn(message, ...) assert @user.invalid? expected = <<~HTML -
Email can't be blank, Email is too short (minimum is 5 characters)
+
Email can't be blank, Email is too short (minimum is 5 characters)
HTML assert_equivalent_html expected, @builder.errors_on(:email) end @@ -1370,8 +1486,8 @@ def warn(message, ...)
- -
can't be blank, is too short (minimum is 5 characters)
+ +
can't be blank, is too short (minimum is 5 characters)
This is required
@@ -1394,9 +1510,9 @@ def warn(message, ...)
- +
-
can't be blank, is too short (minimum is 5 characters)
+
can't be blank, is too short (minimum is 5 characters)
This is required
@@ -1416,7 +1532,7 @@ def warn(message, ...)
- + This is required
@@ -1460,7 +1576,9 @@ def warn(message, ...) @user.email = nil assert @user.invalid? - expected = '
can\'t be blank, is too short (minimum is 5 characters)
' + expected = <<~HTML +
can't be blank, is too short (minimum is 5 characters)
+ HTML assert_equivalent_html expected, @builder.errors_on(:email, hide_attribute_name: true) end @@ -1469,11 +1587,28 @@ def warn(message, ...) @user.email = nil assert @user.invalid? - expected = '
Email can\'t be blank, Email is too short (minimum is 5 characters)
' + expected = <<~HTML +
+ Email can't be blank, Email is too short (minimum is 5 characters) +
+ HTML assert_equivalent_html expected, @builder.errors_on(:email, custom_class: "custom-error-class") end + test "errors_on with specified id:" do + @user.email = nil + assert @user.invalid? + + expected = <<~HTML +
+ Email can't be blank, Email is too short (minimum is 5 characters) +
+ HTML + + assert_equivalent_html expected, @builder.errors_on(:email, id: "custom-id") + end + test "horizontal-style forms" do expected = <<~HTML
diff --git a/test/bootstrap_radio_button_test.rb b/test/bootstrap_radio_button_test.rb index 50b4d6fc..27afd5f9 100644 --- a/test/bootstrap_radio_button_test.rb +++ b/test/bootstrap_radio_button_test.rb @@ -35,11 +35,11 @@ class BootstrapRadioButtonTest < ActionView::TestCase expected = <<~HTML
- + -
error for test
+
error for test
HTML @@ -49,6 +49,25 @@ class BootstrapRadioButtonTest < ActionView::TestCase assert_equivalent_html expected, actual end + test "radio_button with error is wrapped correctly with specified id:" do + @user.errors.add(:misc, "error for test") + expected = <<~HTML +
+
+ + +
error for test
+
+
+ HTML + actual = bootstrap_form_for(@user) do |f| + f.radio_button(:misc, "1", label: "This is a radio button", error_message: true, id: "custom-id") + end + assert_equivalent_html expected, actual + end + test "radio_button disabled label is set correctly" do expected = <<~HTML
diff --git a/test/bootstrap_selects_test.rb b/test/bootstrap_selects_test.rb index 13cbb2e9..a8b23599 100644 --- a/test/bootstrap_selects_test.rb +++ b/test/bootstrap_selects_test.rb @@ -42,8 +42,8 @@ def options_range(start: 1, stop: 31, selected: nil, months: false)
- -
error for test
+ +
error for test
HTML @@ -205,8 +205,8 @@ def options_range(start: 1, stop: 31, selected: nil, months: false)
- -
error for test
+ +
error for test
HTML @@ -285,8 +285,8 @@ def options_range(start: 1, stop: 31, selected: nil, months: false)
- -
error for test
+ +
error for test
HTML @@ -417,16 +417,16 @@ def options_range(start: 1, stop: 31, selected: nil, months: false)
- #{options_range(start: 2007, stop: 2017, selected: 2012)} - #{options_range(start: 1, stop: 12, selected: 2, months: true)} - #{options_range(start: 1, stop: 31, selected: 3)} -
error for test
+
error for test
@@ -435,6 +435,32 @@ def options_range(start: 1, stop: 31, selected: nil, months: false) end end + test "date selects are wrapped correctly with error with specified id:" do + @user.errors.add(:misc, "error for test") + travel_to(Time.utc(2012, 2, 3)) do + expected = <<~HTML +
+
+ +
+ + + +
error for test
+
+
+
+ HTML + assert_equivalent_html expected, bootstrap_form_for(@user) { |f| f.date_select(:misc, id: "custom-id") } + end + end + test "date selects with options are wrapped correctly" do travel_to(Time.utc(2012, 2, 3)) do expected = <<~HTML @@ -520,14 +546,14 @@ def options_range(start: 1, stop: 31, selected: nil, months: false) - #{options_range(start: '00', stop: '23', selected: '12')} : - #{options_range(start: '00', stop: '59', selected: '00')} -
error for test
+
error for test
@@ -536,6 +562,33 @@ def options_range(start: 1, stop: 31, selected: nil, months: false) end end + test "time selects are wrapped correctly with error with specified id:" do + @user.errors.add(:misc, "error for test") + travel_to(Time.utc(2012, 2, 3, 12, 0, 0)) do + expected = <<~HTML +
+
+ +
+ + + + + : + +
error for test
+
+
+
+ HTML + assert_equivalent_html expected, bootstrap_form_for(@user) { |f| f.time_select(:misc, id: "custom-id") } + end + end + test "time selects with options are wrapped correctly" do travel_to(Time.utc(2012, 2, 3, 12, 0, 0)) do expected = <<~HTML @@ -624,24 +677,24 @@ def options_range(start: 1, stop: 31, selected: nil, months: false)
- #{options_range(start: 2007, stop: 2017, selected: 2012)} - #{options_range(start: 1, stop: 12, selected: 2, months: true)} - #{options_range(start: 1, stop: 31, selected: 3)} — - #{options_range(start: '00', stop: '23', selected: '12')} : - #{options_range(start: '00', stop: '59', selected: '00')} -
error for test
+
error for test
@@ -650,6 +703,40 @@ def options_range(start: 1, stop: 31, selected: nil, months: false) end end + test "datetime selects are wrapped correctly with error with specified id:" do + @user.errors.add(:misc, "error for test") + travel_to(Time.utc(2012, 2, 3, 12, 0, 0)) do + expected = <<~HTML +
+
+ +
+ + + + — + + : + +
error for test
+
+
+
+ HTML + assert_equivalent_html expected, bootstrap_form_for(@user) { |f| f.datetime_select(:misc, id: "custom-id") } + end + end + test "datetime selects with options are wrapped correctly" do travel_to(Time.utc(2012, 2, 3, 12, 0, 0)) do expected = <<~HTML diff --git a/test/special_form_class_models_test.rb b/test/special_form_class_models_test.rb index 98550b39..d2857ea8 100644 --- a/test/special_form_class_models_test.rb +++ b/test/special_form_class_models_test.rb @@ -79,9 +79,9 @@ def user_klass.model_name
- +
-
can't be blank
+
can't be blank
HTML assert_equivalent_html expected, @builder.text_field(:password) diff --git a/test/test_helper.rb b/test/test_helper.rb index dcfed122..83fee89e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -79,8 +79,8 @@ def assert_equivalent_html(expected, actual) assert equivalent, lambda { # using a lambda because diffing is expensive Diffy::Diff.new( - expected_html.to_html(indent: 2), - actual_html.to_html(indent: 2) + HtmlBeautifier.beautify(expected_html.to_html(indent: 2)), + HtmlBeautifier.beautify(actual_html.to_html(indent: 2)) ).to_s(:color) } end