diff --git a/Rakefile b/Rakefile index 212b5810e..2531f71e8 100644 --- a/Rakefile +++ b/Rakefile @@ -95,7 +95,7 @@ namespace :docs do require "rails" require "action_controller" require "view_component" - ViewComponent::Base.config.view_component_path = "view_component" + ViewComponent::GlobalConfig.view_component_path = "view_component" require "view_component/docs_builder_component" error_keys = registry.keys.select { |key| key.to_s.include?("Error::MESSAGE") }.map(&:to_s) diff --git a/app/controllers/concerns/view_component/preview_actions.rb b/app/controllers/concerns/view_component/preview_actions.rb index f27d175e6..82e02a45d 100644 --- a/app/controllers/concerns/view_component/preview_actions.rb +++ b/app/controllers/concerns/view_component/preview_actions.rb @@ -47,12 +47,12 @@ def previews # :doc: def default_preview_layout - ViewComponent::Base.config.default_preview_layout + GlobalConfig.default_preview_layout end # :doc: def show_previews? - ViewComponent::Base.config.show_previews + GlobalConfig.show_previews end # :doc: @@ -95,7 +95,7 @@ def prepend_application_view_paths end def prepend_preview_examples_view_path - prepend_view_path(ViewComponent::Base.preview_paths) + prepend_view_path(GlobalConfig.preview_paths) end end end diff --git a/app/helpers/preview_helper.rb b/app/helpers/preview_helper.rb index 18016ff76..6e5baff9d 100644 --- a/app/helpers/preview_helper.rb +++ b/app/helpers/preview_helper.rb @@ -35,7 +35,7 @@ def find_template_data(lookup_context:, template_identifier:) # Fetch template source via finding it through preview paths # to accomodate source view when exclusively using templates # for previews for Rails < 6.1. - all_template_paths = ViewComponent::Base.config.preview_paths.map do |preview_path| + all_template_paths = ViewComponent::GlobalConfig.preview_paths.map do |preview_path| Dir.glob("#{preview_path}/**/*") end.flatten @@ -80,6 +80,6 @@ def prism_language_name_by_template_path(template_file_path:) # :nocov: def serve_static_preview_assets? - ViewComponent::Base.config.show_previews && Rails.application.config.public_file_server.enabled + ViewComponent::GlobalConfig.show_previews && Rails.application.config.public_file_server.enabled end end diff --git a/app/views/view_components/preview.html.erb b/app/views/view_components/preview.html.erb index f364fd725..3101fb00e 100644 --- a/app/views/view_components/preview.html.erb +++ b/app/views/view_components/preview.html.erb @@ -1,5 +1,5 @@ <% if @render_args[:component] %> - <% if ViewComponent::Base.config.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %> + <% if ViewComponent::GlobalConfig.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %> <%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %> <% else %> <%= render_component(@render_args[:component], &@render_args[:block]) %> @@ -8,6 +8,6 @@ <%= render template: @render_args[:template], locals: @render_args[:locals] || {} %> <% end %> -<% if ViewComponent::Base.config.show_previews_source %> +<% if ViewComponent::GlobalConfig.show_previews_source %> <%= preview_source %> <% end %> diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f48c475aa..11bcb3035 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,20 @@ nav_order: 5 ## main +* Add `helpers_enabled` config. + + *Reegan Viljoen* + +* Include ViewComponent::UseHelpers by default. + + *Reegan Viljoen* + +* Make accommodations for component-local config to be introduced in future. + + BREAKING: Assigning to `ViewComponent::Base.config` will no longer work. Instead, assign to `Rails.application.config`. + + *Simon Fish* + * Use struct instead openstruct in lib code. *Oleksii Vasyliev* diff --git a/lib/rails/generators/abstract_generator.rb b/lib/rails/generators/abstract_generator.rb index 8159f4e64..11eed4474 100644 --- a/lib/rails/generators/abstract_generator.rb +++ b/lib/rails/generators/abstract_generator.rb @@ -29,7 +29,7 @@ def file_name end def component_path - ViewComponent::Base.config.view_component_path + GlobalConfig.view_component_path end def stimulus_controller @@ -42,15 +42,15 @@ def stimulus_controller end def sidecar? - options["sidecar"] || ViewComponent::Base.config.generate.sidecar + options["sidecar"] || GlobalConfig.generate.sidecar end def stimulus? - options["stimulus"] || ViewComponent::Base.config.generate.stimulus_controller + options["stimulus"] || GlobalConfig.generate.stimulus_controller end def typescript? - options["typescript"] || ViewComponent::Base.config.generate.typescript + options["typescript"] || GlobalConfig.generate.typescript end end end diff --git a/lib/rails/generators/component/component_generator.rb b/lib/rails/generators/component/component_generator.rb index 4077c78f6..8640cdbcf 100644 --- a/lib/rails/generators/component/component_generator.rb +++ b/lib/rails/generators/component/component_generator.rb @@ -13,12 +13,12 @@ class ComponentGenerator < Rails::Generators::NamedBase check_class_collision suffix: "Component" class_option :inline, type: :boolean, default: false - class_option :locale, type: :boolean, default: ViewComponent::Base.config.generate.locale + class_option :locale, type: :boolean, default: ViewComponent::GlobalConfig.generate.locale class_option :parent, type: :string, desc: "The parent class for the generated component" - class_option :preview, type: :boolean, default: ViewComponent::Base.config.generate.preview + class_option :preview, type: :boolean, default: ViewComponent::GlobalConfig.generate.preview class_option :sidecar, type: :boolean, default: false class_option :stimulus, type: :boolean, - default: ViewComponent::Base.config.generate.stimulus_controller + default: ViewComponent::GlobalConfig.generate.stimulus_controller def create_component_file template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb") @@ -41,7 +41,7 @@ def create_component_file def parent_class return options[:parent] if options[:parent] - ViewComponent::Base.config.component_parent_class || default_parent_class + ViewComponent::GlobalConfig.component_parent_class || default_parent_class end def initialize_signature diff --git a/lib/rails/generators/locale/component_generator.rb b/lib/rails/generators/locale/component_generator.rb index 55f735353..de7193361 100644 --- a/lib/rails/generators/locale/component_generator.rb +++ b/lib/rails/generators/locale/component_generator.rb @@ -12,7 +12,7 @@ class ComponentGenerator < ::Rails::Generators::NamedBase class_option :sidecar, type: :boolean, default: false def create_locale_file - if ViewComponent::Base.config.generate.distinct_locale_files + if ViewComponent::GlobalConfig.generate.distinct_locale_files I18n.available_locales.each do |locale| create_file destination(locale), translations_hash([locale]).to_yaml end diff --git a/lib/rails/generators/preview/component_generator.rb b/lib/rails/generators/preview/component_generator.rb index abb6fbd07..32886421d 100644 --- a/lib/rails/generators/preview/component_generator.rb +++ b/lib/rails/generators/preview/component_generator.rb @@ -4,13 +4,13 @@ module Preview module Generators class ComponentGenerator < ::Rails::Generators::NamedBase source_root File.expand_path("templates", __dir__) - class_option :preview_path, type: :string, desc: "Path for previews, required when multiple preview paths are configured", default: ViewComponent::Base.config.generate.preview_path + class_option :preview_path, type: :string, desc: "Path for previews, required when multiple preview paths are configured", default: ViewComponent::GlobalConfig.generate.preview_path argument :attributes, type: :array, default: [], banner: "attribute" check_class_collision suffix: "ComponentPreview" def create_preview_file - preview_paths = ViewComponent::Base.config.preview_paths + preview_paths = ViewComponent::GlobalConfig.preview_paths optional_path = options[:preview_path] return if preview_paths.count > 1 && optional_path.blank? diff --git a/lib/rails/generators/rspec/component_generator.rb b/lib/rails/generators/rspec/component_generator.rb index 605ada5fb..c804bd20a 100644 --- a/lib/rails/generators/rspec/component_generator.rb +++ b/lib/rails/generators/rspec/component_generator.rb @@ -16,7 +16,7 @@ def create_test_file private def spec_component_path - return "spec/components" unless ViewComponent::Base.config.generate.use_component_path_for_rspec_tests + return "spec/components" unless ViewComponent::GlobalConfig.generate.use_component_path_for_rspec_tests configured_component_path = component_path if configured_component_path.start_with?("app#{File::SEPARATOR}") diff --git a/lib/view_component.rb b/lib/view_component.rb index 62c9cd2cb..df6b8df2b 100644 --- a/lib/view_component.rb +++ b/lib/view_component.rb @@ -13,6 +13,7 @@ module ViewComponent autoload :ComponentError autoload :Config autoload :Deprecation + autoload :GlobalConfig autoload :InlineTemplate autoload :Instrumentation autoload :Preview diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 9e6ddb701..22d9aaa9b 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "action_view" -require "active_support/configurable" require "view_component/collection" require "view_component/compile_cache" require "view_component/compiler" @@ -38,16 +37,15 @@ def config RESERVED_PARAMETER = :content VC_INTERNAL_DEFAULT_FORMAT = :html - # For CSRF authenticity tokens in forms - delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers - - # For Content Security Policy nonces - delegate :content_security_policy_nonce, to: :helpers + # For CSRF authenticity tokens in forms and Content Security Policy nonces + use_helpers :form_authenticity_token, :protect_against_forgery?, :config, :content_security_policy_nonce # Config option that strips trailing whitespace in templates before compiling them. class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2 + delegate :component_config, to: :class + attr_accessor :__vc_original_view_context # Components render in their own view context. Helpers and other functionality @@ -234,7 +232,7 @@ def controller # @return [ActionView::Base] def helpers raise HelpersCalledBeforeRenderError if view_context.nil? - + raise StrictHelperError unless GlobalConfig.helpers_enabled # Attempt to re-use the original view_context passed to the first # component rendered in the rendering pipeline. This prevents the # instantiation of a new view_context via `controller.view_context` which @@ -251,11 +249,19 @@ def method_missing(method_name, *args) # rubocop:disable Style/MissingRespondToM super rescue => e # rubocop:disable Style/RescueStandardError e.set_backtrace e.backtrace.tap(&:shift) - raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && helpers.respond_to?(method_name) - #{e.message} - - You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}'? - MESSAGE + if !GlobalConfig.helpers_enabled + raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && (__vc_original_view_context.respond_to?(method_name) || controller.view_context.respond_to?(method_name)) + #{e.message} + + You may be trying to call a method provided as a view helper. To use it try decalring it using use_helpers :#{method_name}'? + MESSAGE + elsif view_context && e.is_a?(NameError) && helpers.respond_to?(method_name) + raise e, <<~MESSAGE.chomp + #{e.message} + + You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}'? + MESSAGE + end raise end @@ -468,6 +474,7 @@ def sidecar_files(extensions) # view files in a directory named like the component directory = File.dirname(source_location) filename = File.basename(source_location, ".rb") + return [] if name.blank? component_name = name.demodulize.underscore # Add support for nested components defined in the same file. @@ -513,6 +520,16 @@ def inherited(child) # `compile` defines compile + if child.superclass == ViewComponent::Base + child.define_singleton_method(:component_config) do + @@component_config ||= Rails.application.config.view_component.inheritable_copy + end + else + child.define_singleton_method(:component_config) do + @@component_config ||= superclass.component_config.inheritable_copy + end + end + # Give the child its own personal #render_template_for to protect against the case when # eager loading is disabled and the parent component is rendered before the child. In # such a scenario, the parent will override ViewComponent::Base#render_template_for, @@ -545,7 +562,7 @@ def render_template_for(variant = nil, format = nil) # If Rails application is loaded, removes the first part of the path and the extension. if defined?(Rails) && Rails.application child.virtual_path = child.source_location.gsub( - /(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, "" + /(.*#{Regexp.quote(GlobalConfig.view_component_path)})|(\.rb)/, "" ) end diff --git a/lib/view_component/config.rb b/lib/view_component/config.rb index 7133e3d0a..08409738a 100644 --- a/lib/view_component/config.rb +++ b/lib/view_component/config.rb @@ -11,7 +11,7 @@ class << self alias_method :default, :new def defaults - ActiveSupport::OrderedOptions.new.merge!({ + ActiveSupport::InheritableOptions.new({ generate: default_generate_options, preview_controller: "ViewComponentsController", preview_route: "/rails/view_components", @@ -25,7 +25,9 @@ def defaults preview_paths: default_preview_paths, test_controller: "ApplicationController", default_preview_layout: nil, - capture_compatibility_patch_enabled: false + capture_compatibility_patch_enabled: false, + helpers_enabled: true, + strict_helpers_enabled?: false }) end @@ -173,6 +175,11 @@ def defaults # previews. # Defaults to `false`. + # @!attribute helpers_enabled + # @return [Boolean] + # Enables the use of #helpers + # Defaults to `True`. + def default_preview_paths (default_rails_preview_paths + default_rails_engines_preview_paths).uniq end diff --git a/lib/view_component/engine.rb b/lib/view_component/engine.rb index eb56cfa05..fb365eace 100644 --- a/lib/view_component/engine.rb +++ b/lib/view_component/engine.rb @@ -6,7 +6,7 @@ module ViewComponent class Engine < Rails::Engine # :nodoc: - config.view_component = ViewComponent::Config.current + config.view_component = ViewComponent::Config.defaults if Rails.version.to_f < 8.0 rake_tasks do @@ -15,7 +15,7 @@ class Engine < Rails::Engine # :nodoc: else initializer "view_component.stats_directories" do |app| require "rails/code_statistics" - dir = ViewComponent::Base.view_component_path + dir = GlobalConfig.view_component_path Rails::CodeStatistics.register_directory("ViewComponents", dir) end end @@ -23,7 +23,7 @@ class Engine < Rails::Engine # :nodoc: initializer "view_component.set_configs" do |app| options = app.config.view_component - %i[generate preview_controller preview_route show_previews_source].each do |config_option| + %i[generate preview_controller preview_route].each do |config_option| options[config_option] ||= ViewComponent::Base.public_send(config_option) end options.instrumentation_enabled = false if options.instrumentation_enabled.nil? diff --git a/lib/view_component/errors.rb b/lib/view_component/errors.rb index 5b59f6db9..7632d4ae0 100644 --- a/lib/view_component/errors.rb +++ b/lib/view_component/errors.rb @@ -234,6 +234,10 @@ class SystemTestControllerNefariousPathError < BaseError MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory." end + class StrictHelperError < BaseError + MESSAGE = "ViewComponent strict helper mode is enbaled so #helpers is disabled." + end + class AlreadyDefinedPolymorphicSlotSetterError < StandardError MESSAGE = "A method called 'SETTER_METHOD_NAME' already exists and would be overwritten by the 'SETTER_NAME' polymorphic " \ diff --git a/lib/view_component/global_config.rb b/lib/view_component/global_config.rb new file mode 100644 index 000000000..cbe8b0b70 --- /dev/null +++ b/lib/view_component/global_config.rb @@ -0,0 +1,3 @@ +module ViewComponent + GlobalConfig = (defined?(Rails) && Rails.application) ? Rails.application.config.view_component : Base.config # standard:disable Naming/ConstantName +end diff --git a/lib/view_component/instrumentation.rb b/lib/view_component/instrumentation.rb index 41a37ba3e..7a2292616 100644 --- a/lib/view_component/instrumentation.rb +++ b/lib/view_component/instrumentation.rb @@ -23,7 +23,7 @@ def render_in(view_context, &block) private def notification_name - return "!render.view_component" if ViewComponent::Base.config.use_deprecated_instrumentation_name + return "!render.view_component" if GlobalConfig.use_deprecated_instrumentation_name "render.view_component" end diff --git a/lib/view_component/preview.rb b/lib/view_component/preview.rb index 2d3c84465..ef962c918 100644 --- a/lib/view_component/preview.rb +++ b/lib/view_component/preview.rb @@ -109,7 +109,7 @@ def load_previews private def preview_paths - Base.preview_paths + ViewComponent::GlobalConfig.preview_paths end end end diff --git a/lib/view_component/rails/tasks/view_component.rake b/lib/view_component/rails/tasks/view_component.rake index ce2b651c9..4f4942bda 100644 --- a/lib/view_component/rails/tasks/view_component.rake +++ b/lib/view_component/rails/tasks/view_component.rake @@ -7,7 +7,7 @@ namespace :view_component do # :nocov: require "rails/code_statistics" - dir = ViewComponent::Base.view_component_path + dir = ViewComponent::GlobalConfig.view_component_path ::STATS_DIRECTORIES << ["ViewComponents", dir] if File.directory?(Rails.root + dir) # :nocov: end diff --git a/lib/view_component/test_helpers.rb b/lib/view_component/test_helpers.rb index 3677763fb..3ee772a08 100644 --- a/lib/view_component/test_helpers.rb +++ b/lib/view_component/test_helpers.rb @@ -250,7 +250,8 @@ def with_request_url(full_path, host: nil, method: nil, format: ViewComponent::B # # @return [ActionController::Base] def vc_test_controller - @vc_test_controller ||= __vc_test_helpers_build_controller(Base.test_controller.constantize) + config_source = defined?(Rails) ? Rails.application.config.view_component : ViewComponent::Base + @vc_test_controller ||= __vc_test_helpers_build_controller(config_source.test_controller.constantize) end # Access the request used by `render_inline`: diff --git a/lib/view_component/use_helpers.rb b/lib/view_component/use_helpers.rb index 8d395da26..c0a324de5 100644 --- a/lib/view_component/use_helpers.rb +++ b/lib/view_component/use_helpers.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "view_component/errors" + module ViewComponent::UseHelpers extend ActiveSupport::Concern @@ -13,7 +15,7 @@ def use_helper(helper_method, from: nil, prefix: false) class_eval(<<-RUBY, __FILE__, __LINE__ + 1) def #{helper_method_name}(*args, &block) - raise HelpersCalledBeforeRenderError if view_context.nil? + raise ::ViewComponent::HelpersCalledBeforeRenderError if view_context.nil? #{define_helper(helper_method: helper_method, source: from)} end diff --git a/test/sandbox/test/base_test.rb b/test/sandbox/test/base_test.rb index 6a528b74a..7ac880783 100644 --- a/test/sandbox/test/base_test.rb +++ b/test/sandbox/test/base_test.rb @@ -122,6 +122,18 @@ def test_no_method_error_does_not_reference_helper_if_view_context_not_present assert !exception_message_regex.match?(exception.message) end + def test_no_heleprs_error_if_helpers_disabled + with_helpers_enabled_config(false) do + exception = assert_raises(NoMethodError) { Class.new(ViewComponent::Base).new.current_user } + exception_message_regex = Regexp.new <<~MESSAGE.chomp, Regexp::MULTILINE + undefined method `current_user' for .* + + You may be trying to call a method provided as a view helper. To use it try decalring it using use_helpers :current_user'? + MESSAGE + assert !exception_message_regex.match?(exception.message) + end + end + def test_no_method_error_references_helper_if_view_context_present view_context = ActionController::Base.new.view_context view_context.instance_eval { @@ -142,4 +154,21 @@ def test_no_method_error_does_not_reference_missing_helper MESSAGE assert !exception_message_regex.match?(exception.message) end + + def test_strict_helpers_enabled + with_config_option(:strict_helpers_enabled, false) do + top_level_component_class = Class.new(ViewComponent::Base) + refute ViewComponent::Base.config.strict_helpers_enabled?, ".strict_helpers_enabled? should be false by default" + # refute ViewComponent::Base.new.config.strict_helpers_enabled?, "#strict_helpers_enabled? should be false by default" + Rails.application.config.view_component[:strict_helpers_enabled?] = true + refute ViewComponent::Base.config.strict_helpers_enabled?, ".strict_helpers_enabled? should not be changed by global config for ViewComponent::Base" + # refute ViewComponent::Base.new.config.strict_helpers_enabled?, "#strict_helpers_enabled? should not be changed by global config for ViewComponent::Base" + assert top_level_component_class.component_config.strict_helpers_enabled?, ".strict_helpers_enabled? should inherit from global config" + assert top_level_component_class.new.component_config.strict_helpers_enabled?, "#strict_helpers_enabled? should inherit from global config" + inherited_component_class = Class.new(top_level_component_class) + top_level_component_class.component_config[:strict_helpers_enabled?] = false + refute inherited_component_class.component_config.strict_helpers_enabled?, ".strict_helpers_enabled? should inherit from its parent" + refute inherited_component_class.new.component_config.strict_helpers_enabled?, "#strict_helpers_enabled? should inherit from its parent" + end + end end diff --git a/test/sandbox/test/integration_test.rb b/test/sandbox/test/integration_test.rb index 76e65cf4d..cbcae655f 100644 --- a/test/sandbox/test/integration_test.rb +++ b/test/sandbox/test/integration_test.rb @@ -579,7 +579,7 @@ def test_render_component_in_turbo_stream expected_response_body = <<~TURBOSTREAM TURBOSTREAM - if ViewComponent::Base.config.capture_compatibility_patch_enabled + if ViewComponent::GlobalConfig.capture_compatibility_patch_enabled assert_equal expected_response_body, response.body else assert_not_equal expected_response_body, response.body @@ -716,8 +716,8 @@ def test_cached_partial Rails.cache.clear end - def test_config_options_shared_between_base_and_engine - config_entrypoints = [Rails.application.config.view_component, ViewComponent::Base.config] + def test_globalconfig_is_proxy_for_rails_app_config + config_entrypoints = [Rails.application.config.view_component, ViewComponent::GlobalConfig] 2.times do config_entrypoints.first.yield_self do |config| { diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index b06bcf118..7c882b615 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -15,7 +15,7 @@ def test_render_inline_allocations ViewComponent::CompileCache.cache.delete(MyComponent) MyComponent.ensure_compiled - assert_allocations("3.4.0" => 107, "3.3.5" => 116, "3.3.0" => 129, "3.2.5" => 115, "3.1.6" => 115, "3.0.7" => 125) do + assert_allocations("3.4.0" => 108, "3.3.5" => 116, "3.3.0" => 129, "3.2.5" => 115, "3.1.6" => 115, "3.0.7" => 125) do render_inline(MyComponent.new) end @@ -184,6 +184,20 @@ def test_renders_button_to_component ActionController::Base.allow_forgery_protection = old_value end + def test_renders_button_to_component_with_strict_helpers + with_helpers_enabled_config(false) do + old_value = ActionController::Base.allow_forgery_protection + ActionController::Base.allow_forgery_protection = true + + render_inline(ButtonToComponent.new) { "foo" } + + assert_selector("form[class='button_to'][action='/'][method='post']") + assert_selector("input[type='hidden'][name='authenticity_token']", visible: false) + + ActionController::Base.allow_forgery_protection = old_value + end + end + def test_renders_component_with_variant with_variant :phone do render_inline(VariantsComponent.new) @@ -342,6 +356,22 @@ def test_renders_component_with_asset_url assert_match(%r{http://assets.example.com/assets/application-\w+.css}, render_inline(component).text) end + def test_renders_component_with_asset_url_with_strict_helpers + with_helpers_enabled_config(false) do + component = AssetComponent.new + assert_match(%r{http://assets.example.com/assets/application-\w+.css}, render_inline(component).text) + + component.config.asset_host = nil + assert_match(%r{/assets/application-\w+.css}, render_inline(component).text) + + component.config.asset_host = "http://assets.example.com" + assert_match(%r{http://assets.example.com/assets/application-\w+.css}, render_inline(component).text) + + component.config.asset_host = "assets.example.com" + assert_match(%r{http://assets.example.com/assets/application-\w+.css}, render_inline(component).text) + end + end + def test_template_changes_are_not_reflected_if_cache_is_not_cleared render_inline(MyComponent.new) @@ -776,6 +806,14 @@ def test_renders_component_using_rails_config assert_text("http://assets.example.com") end + def test_renders_component_using_rails_config_with_strict_helpers + with_helpers_enabled_config(false) do + render_inline(RailsConfigComponent.new) + + assert_text("http://assets.example.com") + end + end + def test_inherited_component_inherits_template render_inline(InheritedTemplateComponent.new) @@ -1128,6 +1166,14 @@ def test_content_security_policy_nonce assert_selector("script", text: "\n//\n", visible: :hidden) end + def test_content_security_policy_nonce_with_strict_helpers + with_helpers_enabled_config(false) do + render_inline(ContentSecurityPolicyNonceComponent.new) + + assert_selector("script", text: "\n//\n", visible: :hidden) + end + end + def test_use_helper render_inline(UseHelpersComponent.new) assert_selector ".helper__message", text: "Hello helper method" @@ -1220,4 +1266,12 @@ def test_with_format assert_equal(rendered_json["hello"], "world") end end + + def test_strict_helpers + with_helpers_enabled_config(false) do + assert_raises ViewComponent::StrictHelperError do + render_inline(HelpersProxyComponent.new) + end + end + end end diff --git a/test/test_engine/test_helper.rb b/test/test_engine/test_helper.rb index 4dde4ed40..fb5806d89 100644 --- a/test/test_engine/test_helper.rb +++ b/test/test_engine/test_helper.rb @@ -22,7 +22,7 @@ Rails::Generators.namespace = TestEngine -def with_config_option(option_name, new_value, config_entrypoint: TestEngine::Engine.config.view_component) +def with_config_option(option_name, new_value, config_entrypoint: ViewComponent::GlobalConfig) old_value = config_entrypoint.public_send(option_name) config_entrypoint.public_send(:"#{option_name}=", new_value) yield diff --git a/test/test_helper.rb b/test/test_helper.rb index 61e9ff33e..e964927d0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -176,6 +176,19 @@ def with_render_monkey_patch_config(enabled, &block) def with_compiler_development_mode(mode) previous_mode = ViewComponent::Compiler.development_mode ViewComponent::Compiler.development_mode = mode +end + +def with_strict_helpers_config(enabled, &block) + with_config_option(:strict_helpers_enabled, enabled, &block) +end + +def with_helpers_enabled_config(enabled, &block) + with_config_option(:helpers_enabled, enabled, &block) +end + +def with_compiler_mode(mode) + previous_mode = ViewComponent::Compiler.mode + ViewComponent::Compiler.mode = mode yield ensure ViewComponent::Compiler.development_mode = previous_mode