diff --git a/.erb_lint.yml b/.erb_lint.yml index 530f46bdc..6e01701b8 100644 --- a/.erb_lint.yml +++ b/.erb_lint.yml @@ -2,6 +2,7 @@ EnableDefaultLinters: true exclude: - '**/vendor/**/*' + - 'test/sandbox/app/components/content_security_policy_nonce_component.html.erb' linters: ErbSafety: enabled: true diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 38e8dd8c6..c917653d3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,10 @@ nav_order: 6 ## main +* Fix bug where inheritance of components with formatless templates improperly raised a NoMethodError. + + *GitHub Copilot*, *Joel Hawksley*, *Cameron Dutro* + ## 4.6.0 * Add `view_identifier` to the `render.view_component` instrumentation event payload, containing the path to the component's template file (e.g. `app/components/my_component.html.erb`). For components using inline render methods, `view_identifier` will be `nil`. @@ -22,7 +26,7 @@ nav_order: 6 * Return `html_safe` empty string from `render_in` when `render?` is false. - *Copilot* + *GitHub Copilot* ## 4.5.0 diff --git a/lib/view_component/template.rb b/lib/view_component/template.rb index 23ca0c5ee..8cc330075 100644 --- a/lib/view_component/template.rb +++ b/lib/view_component/template.rb @@ -21,6 +21,13 @@ def initialize(component:, details:, lineno: nil, path: nil) class File < Template def initialize(component:, details:, path:) + # If the template file has no format (e.g. .erb instead of .html.erb), + # assume the default format (html). + if details.format.nil? + Kernel.warn("WARNING: Template format for #{path} is missing, defaulting to :html.") + details = ActionView::TemplateDetails.new(details.locale, details.handler, DEFAULT_FORMAT, details.variant) + end + @strip_annotation_line = false # Rails 8.1 added a newline to compiled ERB output (rails/rails#53731). diff --git a/test/sandbox/app/components/content_security_policy_nonce_component.erb b/test/sandbox/app/components/content_security_policy_nonce_component.html.erb similarity index 100% rename from test/sandbox/app/components/content_security_policy_nonce_component.erb rename to test/sandbox/app/components/content_security_policy_nonce_component.html.erb diff --git a/test/sandbox/app/components/format_less_child_component.erb b/test/sandbox/app/components/format_less_child_component.erb new file mode 100644 index 000000000..3b6bbb6e7 --- /dev/null +++ b/test/sandbox/app/components/format_less_child_component.erb @@ -0,0 +1 @@ +
<%= content %>
diff --git a/test/sandbox/app/components/format_less_child_component.rb b/test/sandbox/app/components/format_less_child_component.rb new file mode 100644 index 000000000..6e86972eb --- /dev/null +++ b/test/sandbox/app/components/format_less_child_component.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class FormatLessChildComponent < FormatLessParentComponent +end diff --git a/test/sandbox/app/components/format_less_parent_component.erb b/test/sandbox/app/components/format_less_parent_component.erb new file mode 100644 index 000000000..34bdb107d --- /dev/null +++ b/test/sandbox/app/components/format_less_parent_component.erb @@ -0,0 +1 @@ +
<%= content %>
diff --git a/test/sandbox/app/components/format_less_parent_component.rb b/test/sandbox/app/components/format_less_parent_component.rb new file mode 100644 index 000000000..7ec22277a --- /dev/null +++ b/test/sandbox/app/components/format_less_parent_component.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class FormatLessParentComponent < ViewComponent::Base +end diff --git a/test/sandbox/app/components/invalid_named_parameters_component.erb b/test/sandbox/app/components/invalid_named_parameters_component.html.erb similarity index 100% rename from test/sandbox/app/components/invalid_named_parameters_component.erb rename to test/sandbox/app/components/invalid_named_parameters_component.html.erb diff --git a/test/sandbox/app/components/invalid_parameters_component.erb b/test/sandbox/app/components/invalid_parameters_component.html.erb similarity index 100% rename from test/sandbox/app/components/invalid_parameters_component.erb rename to test/sandbox/app/components/invalid_parameters_component.html.erb diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index b394bba4d..ee98488ae 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -852,6 +852,18 @@ def test_inherited_component_overrides_inherits_template assert_selector("div", text: "hello, my own template") end + def test_inherited_component_with_format_less_template + # Reproduces https://github.com/ViewComponent/view_component/issues/2573 + # When a parent component uses a format-less template (e.g. .erb or .slim + # instead of .html.erb or .html.slim), the child component crashes with + # "undefined method 'upcase' for nil" during compilation. + render_inline(FormatLessChildComponent.new) do + "Hello World" + end + + assert_selector("div.child", text: "Hello World") + end + def test_inherited_inline_component_inherits_inline_method render_inline(InlineInheritedComponent.new) diff --git a/test/test_helper.rb b/test/test_helper.rb index 13353dbd6..f6ddbd2e5 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -14,6 +14,7 @@ module Warning def self.warn(message) called_by = caller_locations(1, 1).first.path return super unless called_by&.start_with?(PROJECT_ROOT) && !called_by.start_with?("#{PROJECT_ROOT}/vendor") + return super if message.include?("Template format for") raise "Warning: #{message}" end