diff --git a/Rakefile b/Rakefile index 212b5810e..9c2e975ce 100644 --- a/Rakefile +++ b/Rakefile @@ -82,7 +82,7 @@ namespace :docs do instance_methods_to_document = meths.select { |method| method.scope != :class } class_methods_to_document = meths.select { |method| method.scope == :class } - configuration_methods_to_document = registry.get("ViewComponent::Config").meths.select(&:reader?) + configuration_methods_to_document = registry.get("ViewComponent::ApplicationConfig").meths.select(&:reader?) test_helper_methods_to_document = registry .get("ViewComponent::TestHelpers") .meths diff --git a/app/controllers/concerns/view_component/preview_actions.rb b/app/controllers/concerns/view_component/preview_actions.rb index 1a4e7536e..0f66c1d66 100644 --- a/app/controllers/concerns/view_component/preview_actions.rb +++ b/app/controllers/concerns/view_component/preview_actions.rb @@ -54,12 +54,12 @@ def previews # :doc: def default_preview_layout - ViewComponent::Base.config.default_preview_layout + Rails.application.config.view_component.previews.default_layout end # :doc: def show_previews? - ViewComponent::Base.config.show_previews + Rails.application.config.view_component.previews.show end # :doc: @@ -102,7 +102,7 @@ def prepend_application_view_paths end def prepend_preview_examples_view_path - prepend_view_path(ViewComponent::Base.preview_paths) + prepend_view_path(Rails.application.config.view_component.previews.paths) end end end diff --git a/app/helpers/preview_helper.rb b/app/helpers/preview_helper.rb index edc232a4e..86e3fb991 100644 --- a/app/helpers/preview_helper.rb +++ b/app/helpers/preview_helper.rb @@ -48,6 +48,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 + Rails.application.config.view_component.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 c12aa6eb0..71e555c94 100644 --- a/app/views/view_components/preview.html.erb +++ b/app/views/view_components/preview.html.erb @@ -4,6 +4,6 @@ <%= render template: @render_args[:template], locals: @render_args[:locals] || {} %> <% end %> -<% if ViewComponent::Base.config.show_previews_source %> +<% if Rails.application.config.view_component.previews.show_source %> <%= preview_source %> <% end %> diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ac7b9f2a5..8887e2b69 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,10 @@ nav_order: 5 ## 4.0.0 +* BREAKING: Make most configuration local to component instances. + + Simon Fish + * BREAKING: Require [non-EOL](https://endoflife.date/rails) Rails (`>= 7.1.0`). *Joel Hawksley* diff --git a/lib/rails/generators/abstract_generator.rb b/lib/rails/generators/abstract_generator.rb index 8159f4e64..fad034ed0 100644 --- a/lib/rails/generators/abstract_generator.rb +++ b/lib/rails/generators/abstract_generator.rb @@ -29,7 +29,8 @@ def file_name end def component_path - ViewComponent::Base.config.view_component_path + # FIXME: Doesn't yet handle multiple component paths + Rails.application.config.view_component.generate.view_component_paths!.first end def stimulus_controller @@ -42,15 +43,15 @@ def stimulus_controller end def sidecar? - options["sidecar"] || ViewComponent::Base.config.generate.sidecar + options["sidecar"] || Rails.application.config.view_component.generate.sidecar end def stimulus? - options["stimulus"] || ViewComponent::Base.config.generate.stimulus_controller + options["stimulus"] || Rails.application.config.view_component.generate.stimulus_controller end def typescript? - options["typescript"] || ViewComponent::Base.config.generate.typescript + options["typescript"] || Rails.application.config.view_component.generate.typescript end end end diff --git a/lib/rails/generators/component/component_generator.rb b/lib/rails/generators/component/component_generator.rb index d5e4b8b3c..a96cd9615 100644 --- a/lib/rails/generators/component/component_generator.rb +++ b/lib/rails/generators/component/component_generator.rb @@ -13,12 +13,13 @@ 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 :parent, type: :string, desc: "The parent class for the generated component" - class_option :preview, type: :boolean, default: ViewComponent::Base.config.generate.preview - class_option :sidecar, type: :boolean, default: false + class_option :locale, type: :boolean, default: Rails.application.config.view_component.generate.locale + class_option :parent, type: :string, desc: "The parent class for the generated component", + default: Rails.application.config.view_component.generate.component_parent_class + class_option :preview, type: :boolean, default: Rails.application.config.view_component.generate.preview + class_option :sidecar, type: :boolean, default: Rails.application.config.view_component.generate.sidecar class_option :stimulus, type: :boolean, - default: ViewComponent::Base.config.generate.stimulus_controller + default: Rails.application.config.view_component.generate.stimulus_controller class_option :skip_suffix, type: :boolean, default: false def create_component_file @@ -42,7 +43,7 @@ def create_component_file def parent_class return options[:parent] if options[:parent] - ViewComponent::Base.config.component_parent_class || default_parent_class + Rails.application.config.view_component.generate.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..f713d3473 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 Rails.application.config.view_component.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..308c25187 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: Rails.application.config.view_component.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 = Rails.application.config.view_component.previews.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..cf6ce2b18 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 Rails.application.config.view_component.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/rails/generators/stimulus/component_generator.rb b/lib/rails/generators/stimulus/component_generator.rb index f81912ddf..6b25bbf50 100644 --- a/lib/rails/generators/stimulus/component_generator.rb +++ b/lib/rails/generators/stimulus/component_generator.rb @@ -8,8 +8,8 @@ class ComponentGenerator < ::Rails::Generators::NamedBase include ViewComponent::AbstractGenerator source_root File.expand_path("templates", __dir__) - class_option :sidecar, type: :boolean, default: false - class_option :typescript, type: :boolean, default: false + class_option :sidecar, type: :boolean, default: Rails.application.config.view_component.generate.sidecar + class_option :typescript, type: :boolean, default: Rails.application.config.view_component.generate.typescript def create_stimulus_controller template "component_controller.#{filetype}", destination diff --git a/lib/view_component.rb b/lib/view_component.rb index 62c9cd2cb..d8ae6ba16 100644 --- a/lib/view_component.rb +++ b/lib/view_component.rb @@ -6,12 +6,12 @@ module ViewComponent extend ActiveSupport::Autoload + autoload :ApplicationConfig autoload :Base autoload :CaptureCompatibility autoload :Compiler autoload :CompileCache autoload :ComponentError - autoload :Config autoload :Deprecation autoload :InlineTemplate autoload :Instrumentation diff --git a/lib/view_component/config.rb b/lib/view_component/application_config.rb similarity index 72% rename from lib/view_component/config.rb rename to lib/view_component/application_config.rb index c2c8d6620..fda5eba12 100644 --- a/lib/view_component/config.rb +++ b/lib/view_component/application_config.rb @@ -3,7 +3,7 @@ require "view_component/deprecation" module ViewComponent - class Config + class ApplicationConfig class << self # `new` without any arguments initializes the default configuration, but # it's important to differentiate in case that's no longer the case in @@ -11,21 +11,31 @@ class << self alias_method :default, :new def defaults - ActiveSupport::OrderedOptions.new.merge!({ - generate: default_generate_options, - preview_controller: "ViewComponentsController", - preview_route: "/rails/view_components", - show_previews_source: false, + ActiveSupport::OrderedOptions[ + generate: ActiveSupport::OrderedOptions[ + sidecar: false, + stimulus_controller: false, + typescript: false, + locale: false, + distinct_locale_files: false, + preview: false, + preview_path: "", + use_component_path_for_rspec_tests: false, + view_component_paths: ["app/components"], + component_parent_class: nil + ], + previews: ActiveSupport::OrderedOptions[ + show: (Rails.env.development? || Rails.env.test?), + controller: "ViewComponentsController", + route: "/rails/view_components", + show_source: (Rails.env.development? || Rails.env.test?), + paths: default_preview_paths, + default_layout: nil + ], instrumentation_enabled: false, - use_deprecated_instrumentation_name: true, - view_component_path: "app/components", - component_parent_class: nil, - show_previews: Rails.env.development? || Rails.env.test?, - preview_paths: default_preview_paths, test_controller: "ApplicationController", - default_preview_layout: nil, - capture_compatibility_patch_enabled: false - }) + capture_compatibility_patch_enabled: false, + ] end # @!attribute generate @@ -97,68 +107,71 @@ def defaults # For example, if the `view_component_path` is # `app/views/components`, then the generator will create a new spec file # in `spec/views/components/` rather than the default `spec/components/`. + # + # #### `#view_component_path`` + # + # The path in which components, their templates, and their sidecars should + # be stored: + # + # config.view_component.generate.view_component_paths = "app/components" + # + # Defaults to `"app/components"`. + # TODO: It looks like this was actually the default path generators would use. + # I think it's used elsewhere inside `base.rb` for some reason, though. + # + # #### `#component_parent_class` + # + # The parent class from which generated components will inherit. + # Defaults to `nil`. If this is falsy, generators will use + # `"ApplicationComponent"` if defined, `"ViewComponent::Base"` otherwise. - # @!attribute preview_controller + + # @!attribute previews # @return [String] + # The subset of configuration options relating to previews. + # + # #### `#show` + # + # Whether component previews are enabled. + # Defaults to `true` in development and test environments. + # + # #### `#controller` + # # The controller used for previewing components. # Defaults to `ViewComponentsController`. - - # @!attribute preview_route - # @return [String] + # + # #### `route` + # # The entry route for component previews. # Defaults to `"/rails/view_components"`. - - # @!attribute show_previews_source - # @return [Boolean] + # + # #### `show_source` + # # Whether to display source code previews in component previews. # Defaults to `false`. + # + # #### `paths` + # + # The locations in which component previews will be looked up. + # Defaults to `['test/components/previews']` relative to your Rails root. + # + # #### `#default_layout` + # + # A custom default layout used for the previews index page and individual + # previews. + # Defaults to `nil`. If this is falsy, `"component_preview"` is used. # @!attribute instrumentation_enabled # @return [Boolean] # Whether ActiveSupport notifications are enabled. # Defaults to `false`. - # @!attribute use_deprecated_instrumentation_name - # @return [Boolean] - # Whether ActiveSupport Notifications use the private name `"!render.view_component"` - # or are made more publicly available via `"render.view_component"`. - # Will default to `false` in next major version. - # Defaults to `true`. - - # @!attribute view_component_path - # @return [String] - # The path in which components, their templates, and their sidecars should - # be stored. - # Defaults to `"app/components"`. - - # @!attribute component_parent_class - # @return [String] - # The parent class from which generated components will inherit. - # Defaults to `nil`. If this is falsy, generators will use - # `"ApplicationComponent"` if defined, `"ViewComponent::Base"` otherwise. - - # @!attribute show_previews - # @return [Boolean] - # Whether component previews are enabled. - # Defaults to `true` in development and test environments. - - # @!attribute preview_paths - # @return [Array] - # The locations in which component previews will be looked up. - # Defaults to `['test/components/previews']` relative to your Rails root. - # @!attribute test_controller # @return [String] # The controller used for testing components. # Can also be configured on a per-test basis using `#with_controller_class`. # Defaults to `ApplicationController`. - # @!attribute default_preview_layout - # @return [String] - # A custom default layout used for the previews index page and individual - # previews. - # Defaults to `nil`. If this is falsy, `"component_preview"` is used. - # @!attribute capture_compatibility_patch_enabled # @return [Boolean] # Enables the experimental capture compatibility patch that makes ViewComponent @@ -171,8 +184,6 @@ def default_preview_paths end def default_rails_preview_paths - return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews") - ["#{Rails.root}/test/components/previews"] end @@ -189,22 +200,8 @@ def registered_rails_engines_with_previews defined?(descendant.root) && Dir.exist?("#{descendant.root}/test/components/previews") end end - - def default_generate_options - options = ActiveSupport::OrderedOptions.new(false) - options.preview_path = "" - options - end end - # @!attribute current - # @return [ViewComponent::Config] - # Returns the current ViewComponent::Config. This is persisted against this - # class so that config options remain accessible before the rest of - # ViewComponent has loaded. Defaults to an instance of ViewComponent::Config - # with all other documented defaults set. - class_attribute :current, default: defaults, instance_predicate: false - def initialize @config = self.class.defaults end diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 5099901a9..745b283cb 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -2,10 +2,10 @@ require "action_view" require "active_support/configurable" +require "view_component/application_config" require "view_component/collection" require "view_component/compile_cache" require "view_component/compiler" -require "view_component/config" require "view_component/errors" require "view_component/inline_template" require "view_component/preview" @@ -19,17 +19,40 @@ module ViewComponent class Base < ActionView::Base - class << self - delegate(*ViewComponent::Config.defaults.keys, to: :config) + class Configuration + def initialize + @config = ActiveSupport::OrderedOptions[ + test_controller: "ApplicationController" + ] + end - # Returns the current config. - # - # @return [ActiveSupport::OrderedOptions] - def config - ViewComponent::Config.current + delegate_missing_to :@config + + def deep_dup + new.instance_variable_set(:@config, @config.deep_dup) + end + end + # Returns the current config. + # + # @return [ActiveSupport::OrderedOptions] + def self.configuration + @_configuration ||= if respond_to?(:superclass) && superclass.respond_to?(:configuration) + superclass.configuration.inheritable_copy + else + # create a new "anonymous" class that will host the compiled reader methods + Class.new(ActiveSupport::Configurable::Configuration).new end end + def configuration + @_configuration ||= self.class.configuration.inheritable_copy + end + + def self.configure(&block) + configuration.instance_eval(&block) + configuration.compile_methods! + end + include ViewComponent::InlineTemplate include ViewComponent::UseHelpers include ViewComponent::Slotable @@ -39,6 +62,10 @@ def config RESERVED_PARAMETER = :content VC_INTERNAL_DEFAULT_FORMAT = :html + def config + Rails.application.config + end + # For CSRF authenticity tokens in forms delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers @@ -542,10 +569,12 @@ def render_template_for(requested_details) child.identifier = caller_locations(1, 10).reject { |l| l.base_label == "inherited" }[0].path # If Rails application is loaded, removes the first part of the path and the extension. + # TODO: probably a way to rework this, seems like it's load order dependent at the moment? if defined?(Rails) && Rails.application - child.virtual_path = child.identifier.gsub( - /(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, "" - ) + child.virtual_path = Rails.application.config.view_component.generate.view_component_paths! + .inject(child.identifier) do |identifier, path| + identifier.gsub(/.*#{Regexp.quote(path)}/, "") + end end # Set collection parameter to the extended component @@ -558,6 +587,10 @@ def render_template_for(requested_details) child.instance_variable_set(:@__vc_ancestor_calls, vc_ancestor_calls) end + child.class_eval do + @_configuration = nil + end + super end diff --git a/lib/view_component/engine.rb b/lib/view_component/engine.rb index 40b9d305c..25473cdf5 100644 --- a/lib/view_component/engine.rb +++ b/lib/view_component/engine.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true require "rails" -require "view_component/config" +require "view_component/application_config" require "view_component/deprecation" module ViewComponent class Engine < Rails::Engine # :nodoc: - config.view_component = ViewComponent::Config.current + config.view_component = ViewComponent::ApplicationConfig.default if Rails.version.to_f < 8.0 rake_tasks do @@ -16,8 +16,8 @@ class Engine < Rails::Engine # :nodoc: initializer "view_component.stats_directories" do |app| require "rails/code_statistics" - if Rails.root.join(ViewComponent::Base.view_component_path).directory? - Rails::CodeStatistics.register_directory("ViewComponents", ViewComponent::Base.view_component_path) + if Rails.root.join(Rails.application.config.view_component.generate.view_component_paths!.first).directory? + Rails::CodeStatistics.register_directory("ViewComponents", Rails.application.config.view_component.generate.view_component_paths!.first) end if Rails.root.join("test/components").directory? @@ -29,24 +29,20 @@ 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| - options[config_option] ||= ViewComponent::Base.public_send(config_option) - end - options.instrumentation_enabled = false if options.instrumentation_enabled.nil? - options.show_previews = (Rails.env.development? || Rails.env.test?) if options.show_previews.nil? - - if options.show_previews - # This is still necessary because when `config.view_component` is declared, `Rails.root` is unspecified. - options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?( - "#{Rails.root}/test/components/previews" - ) + # This is still necessary because when `config.view_component` is declared, `Rails.root` is unspecified. + options.previews.paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && ( + "#{Rails.root}/test/components/previews" + ) + + # TODO: Custom error type, more informative error here + # Also maybe there's a better time to call this. + # raise "Preview directories must exist" if options.show_previews && !options.preview_paths.all? { |path| Dir.exist?(path) } - if options.show_previews_source - require "method_source" + if options.previews.show && options.previews.show_source + require "method_source" - app.config.to_prepare do - MethodSource.instance_variable_set(:@lines_for_file, {}) - end + app.config.to_prepare do + MethodSource.instance_variable_set(:@lines_for_file, {}) end end end @@ -56,12 +52,6 @@ class Engine < Rails::Engine # :nodoc: if app.config.view_component.instrumentation_enabled.present? # :nocov: Re-executing the below in tests duplicates initializers and causes order-dependent failures. ViewComponent::Base.prepend(ViewComponent::Instrumentation) - if app.config.view_component.use_deprecated_instrumentation_name - ViewComponent::Deprecation.deprecation_warning( - "!render.view_component", - "Use the new instrumentation key `render.view_component` instead. See https://viewcomponent.org/guide/instrumentation.html" - ) - end # :nocov: end end @@ -78,8 +68,8 @@ class Engine < Rails::Engine # :nodoc: initializer "view_component.set_autoload_paths" do |app| options = app.config.view_component - if options.show_previews && !options.preview_paths.empty? - paths_to_add = options.preview_paths - ActiveSupport::Dependencies.autoload_paths + if options.previews.show && !options.previews.paths.empty? + paths_to_add = options.previews.paths - ActiveSupport::Dependencies.autoload_paths ActiveSupport::Dependencies.autoload_paths.concat(paths_to_add) if paths_to_add.any? end end @@ -97,7 +87,7 @@ class Engine < Rails::Engine # :nodoc: end def serve_static_preview_assets?(app_config) - app_config.view_component.show_previews && app_config.public_file_server.enabled + app_config.view_component.previews.show && app_config.public_file_server.enabled end initializer "compiler mode" do |_app| @@ -107,19 +97,19 @@ def serve_static_preview_assets?(app_config) config.after_initialize do |app| options = app.config.view_component - if options.show_previews + if options.previews.show app.routes.prepend do - preview_controller = options.preview_controller.sub(/Controller$/, "").underscore + preview_controller = options.previews.controller!.sub(/Controller$/, "").underscore get( - options.preview_route, + options.previews.route!, to: "#{preview_controller}#index", as: :preview_view_components, internal: true ) get( - "#{options.preview_route}/*path", + "#{options.previews.route!}/*path", to: "#{preview_controller}#previews", as: :preview_view_component, internal: true diff --git a/lib/view_component/instrumentation.rb b/lib/view_component/instrumentation.rb index d63efd283..96ed96390 100644 --- a/lib/view_component/instrumentation.rb +++ b/lib/view_component/instrumentation.rb @@ -10,7 +10,7 @@ def self.included(mod) def render_in(view_context, &block) ActiveSupport::Notifications.instrument( - notification_name, + "render.view_component", { name: self.class.name, identifier: self.class.identifier @@ -19,13 +19,5 @@ def render_in(view_context, &block) super end end - - private - - def notification_name - return "!render.view_component" if ViewComponent::Base.config.use_deprecated_instrumentation_name - - "render.view_component" - end end end diff --git a/lib/view_component/preview.rb b/lib/view_component/preview.rb index 737b3601e..808a4e954 100644 --- a/lib/view_component/preview.rb +++ b/lib/view_component/preview.rb @@ -107,7 +107,7 @@ def load_previews private def preview_paths - Base.preview_paths + Rails.application.config.view_component.previews.paths end end end diff --git a/lib/view_component/test_helpers.rb b/lib/view_component/test_helpers.rb index 6b6ace7b2..04a21a76d 100644 --- a/lib/view_component/test_helpers.rb +++ b/lib/view_component/test_helpers.rb @@ -84,7 +84,7 @@ def rendered_json # @param params [Hash] Parameters to be passed to the preview. # @return [Nokogiri::HTML] def render_preview(name, from: __vc_test_helpers_preview_class, params: {}) - previews_controller = __vc_test_helpers_build_controller(Rails.application.config.view_component.preview_controller.constantize) + previews_controller = __vc_test_helpers_build_controller(Rails.application.config.view_component.previews.controller.constantize) # From what I can tell, it's not possible to overwrite all request parameters # at once, so we set them individually here. @@ -245,7 +245,7 @@ def with_request_url(full_path, host: nil, method: nil) # # @return [ActionController::Base] def vc_test_controller - @vc_test_controller ||= __vc_test_helpers_build_controller(Base.test_controller.constantize) + @vc_test_controller ||= __vc_test_helpers_build_controller(Rails.application.config.view_component.test_controller.constantize) end # Access the request used by `render_inline`: diff --git a/test/sandbox/app/components/config_base_component.rb b/test/sandbox/app/components/config_base_component.rb new file mode 100644 index 000000000..a2fe47ac1 --- /dev/null +++ b/test/sandbox/app/components/config_base_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class ConfigBaseComponent < ViewComponent::Base + configure do |config| + config.test_controller = "SomeController" + end +end diff --git a/test/sandbox/app/components/inherited_config_component.rb b/test/sandbox/app/components/inherited_config_component.rb new file mode 100644 index 000000000..349c2d0df --- /dev/null +++ b/test/sandbox/app/components/inherited_config_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class InheritedConfigComponent < ConfigBaseComponent + configure do |config| + config.test_controller = "AnotherController" + end +end diff --git a/test/sandbox/config/application.rb b/test/sandbox/config/application.rb index a92e48278..dc0c991f3 100644 --- a/test/sandbox/config/application.rb +++ b/test/sandbox/config/application.rb @@ -42,7 +42,7 @@ class Application < Rails::Application # Prepare test_set_no_duplicate_autoload_paths config.autoload_paths.push("#{config.root}/my/components/previews") - config.view_component.preview_paths << "#{config.root}/my/components/previews" + config.view_component.previews.paths << "#{config.root}/my/components/previews" end end diff --git a/test/sandbox/config/environments/test.rb b/test/sandbox/config/environments/test.rb index 857bb1676..a7b244929 100644 --- a/test/sandbox/config/environments/test.rb +++ b/test/sandbox/config/environments/test.rb @@ -30,10 +30,10 @@ # Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false - config.view_component.show_previews = true + config.view_component.previews.show = true - config.view_component.preview_paths << "#{Rails.root}/lib/component_previews" - config.view_component.show_previews_source = true + config.view_component.previews.paths << "#{Rails.root}/lib/component_previews" + config.view_component.previews.show_source = true config.view_component.test_controller = "IntegrationExamplesController" config.view_component.capture_compatibility_patch_enabled = ENV["CAPTURE_PATCH_ENABLED"] == "true" diff --git a/test/sandbox/test/base_test.rb b/test/sandbox/test/base_test.rb index ac84a5a79..9cd75e3be 100644 --- a/test/sandbox/test/base_test.rb +++ b/test/sandbox/test/base_test.rb @@ -145,4 +145,12 @@ def test_no_method_error_does_not_reference_missing_helper MESSAGE assert !exception_message_regex.match?(exception.message) end + + def test_configuration_dsl + assert_equal "SomeController", ConfigBaseComponent.new.configuration.test_controller + end + + def test_inherited_configuration + assert_equal "AnotherController", InheritedConfigComponent.new.configuration.test_controller + end end diff --git a/test/sandbox/test/config_test.rb b/test/sandbox/test/config_test.rb index 0a74018b4..ec87cb340 100644 --- a/test/sandbox/test/config_test.rb +++ b/test/sandbox/test/config_test.rb @@ -5,18 +5,29 @@ module ViewComponent class ConfigTest < TestCase def setup - @config = ViewComponent::Config.new + @config = ViewComponent::ApplicationConfig.new end def test_defaults_are_correct - assert_equal @config.generate, {preview_path: ""} - assert_equal @config.preview_controller, "ViewComponentsController" - assert_equal @config.preview_route, "/rails/view_components" - assert_equal @config.show_previews_source, false + assert_equal @config.generate, { + sidecar: false, + stimulus_controller: false, + typescript: false, + locale: false, + distinct_locale_files: false, + preview: false, + preview_path: "", + use_component_path_for_rspec_tests: false, + view_component_paths: ["app/components"], + component_parent_class: nil, + } + assert_equal @config.previews.controller, "ViewComponentsController" + assert_equal @config.previews.route, "/rails/view_components" + assert_equal @config.previews.show_source, true assert_equal @config.instrumentation_enabled, false - assert_equal @config.use_deprecated_instrumentation_name, true - assert_equal @config.show_previews, true - assert_equal @config.preview_paths, ["#{Rails.root}/test/components/previews"] + assert_equal @config.capture_compatibility_patch_enabled, false + assert_equal @config.previews.show, true + assert_equal @config.previews.paths, ["#{Rails.root}/test/components/previews"] end def test_all_methods_are_documented @@ -28,9 +39,9 @@ def test_all_methods_are_documented Rake::Task["yard"].execute configuration_methods_to_document = YARD::RegistryStore.new.tap do |store| store.load!(".yardoc") - end.get("ViewComponent::Config").meths.select(&:reader?).reject { |meth| meth.name == :config } - default_options = ViewComponent::Config.defaults.keys - accessors = ViewComponent::Config.instance_methods(false).reject do |method_name| + end.get("ViewComponent::ApplicationConfig").meths.select(&:reader?).reject { |meth| meth.name == :config } + default_options = ViewComponent::ApplicationConfig.defaults.keys + accessors = ViewComponent::ApplicationConfig.instance_methods(false).reject do |method_name| method_name.to_s.end_with?("=") || method_name == :method_missing end options_defined_on_instance = Set[*default_options, *accessors] diff --git a/test/sandbox/test/instrumentation_test.rb b/test/sandbox/test/instrumentation_test.rb index b2d4d0490..65ad22103 100644 --- a/test/sandbox/test/instrumentation_test.rb +++ b/test/sandbox/test/instrumentation_test.rb @@ -4,29 +4,16 @@ class InstrumentationTest < ViewComponent::TestCase def test_instrumentation - with_config_option(:use_deprecated_instrumentation_name, false) do - events = [] - ActiveSupport::Notifications.subscribe("render.view_component") do |*args| - events << ActiveSupport::Notifications::Event.new(*args) - end - render_inline(InstrumentationComponent.new) - - assert_selector("div", text: "hello,world!") - assert_equal(events.size, 1) - assert_equal("render.view_component", events[0].name) - assert_equal(events[0].payload[:name], "InstrumentationComponent") - assert_match("app/components/instrumentation_component.rb", events[0].payload[:identifier]) - end - end - - def test_instrumentation_with_deprecated_name events = [] - ActiveSupport::Notifications.subscribe("!render.view_component") do |*args| + ActiveSupport::Notifications.subscribe("render.view_component") do |*args| events << ActiveSupport::Notifications::Event.new(*args) end render_inline(InstrumentationComponent.new) + assert_selector("div", text: "hello,world!") assert_equal(events.size, 1) - assert_equal("!render.view_component", events[0].name) + assert_equal("render.view_component", events[0].name) + assert_equal(events[0].payload[:name], "InstrumentationComponent") + assert_match("app/components/instrumentation_component.rb", events[0].payload[:identifier]) end end diff --git a/test/sandbox/test/integration_test.rb b/test/sandbox/test/integration_test.rb index bf567e25b..5b73867f7 100644 --- a/test/sandbox/test/integration_test.rb +++ b/test/sandbox/test/integration_test.rb @@ -539,7 +539,7 @@ def test_render_component_in_turbo_stream expected_response_body = <<~TURBOSTREAM TURBOSTREAM - if ViewComponent::Base.config.capture_compatibility_patch_enabled + if Rails.application.config.view_component.capture_compatibility_patch_enabled assert_equal expected_response_body, response.body else assert_not_equal expected_response_body, response.body @@ -670,25 +670,6 @@ 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] - 2.times do - config_entrypoints.first.yield_self do |config| - { - generate: config.generate.dup.tap { |c| c.sidecar = true }, - preview_controller: "SomeOtherController", - preview_route: "/some/other/route", - show_previews_source: true - }.each do |option, value| - with_config_option(option, value, config_entrypoint: config) do - assert_equal(config.public_send(option), config_entrypoints.second.public_send(option)) - end - end - end - config_entrypoints.rotate! - end - end - def test_path_traversal_raises_error path = "../../README.md" diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index d19863f40..e5b094fd7 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -16,8 +16,8 @@ def test_render_inline_allocations MyComponent.ensure_compiled allocations = (Rails.version.to_f >= 8.0) ? - {"3.5.0" => 104, "3.4.1" => 104, "3.3.7" => 108} : - {"3.3.7" => 107, "3.3.0" => 120, "3.2.7" => 105, "3.1.6" => 118, "3.0.7" => 127} + {"3.5.0" => 107, "3.4.1" => 107, "3.3.7" => 111} : + {"3.3.7" => 110, "3.3.0" => 123, "3.2.7" => 108, "3.1.6" => 121, "3.0.7" => 130} assert_allocations(**allocations) do render_inline(MyComponent.new) diff --git a/test/test_helper.rb b/test/test_helper.rb index 5ea189c8d..1dcc4e086 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -68,35 +68,39 @@ def with_config_option(option_name, new_value, config_entrypoint: Rails.applicat # @yield Test code to run # @return [void] def with_preview_paths(new_value, &block) - with_config_option(:preview_paths, new_value, &block) + old_value = Rails.application.config.view_component.previews.paths + Rails.application.config.view_component.previews.paths = new_value + yield +ensure + Rails.application.config.view_component.previews.paths = old_value end def with_preview_route(new_value) - old_value = Rails.application.config.view_component.preview_route - Rails.application.config.view_component.preview_route = new_value + old_value = Rails.application.config.view_component.previews.route + Rails.application.config.view_component.previews.route = new_value app.reloader.reload! yield ensure - Rails.application.config.view_component.preview_route = old_value + Rails.application.config.view_component.previews.route = old_value app.reloader.reload! end def with_preview_controller(new_value) - old_value = Rails.application.config.view_component.preview_controller - Rails.application.config.view_component.preview_controller = new_value + old_value = Rails.application.config.view_component.previews.controller + Rails.application.config.view_component.previews.controller = new_value app.reloader.reload! yield ensure - Rails.application.config.view_component.preview_controller = old_value + Rails.application.config.view_component.previews.controller = old_value app.reloader.reload! end def with_custom_component_path(new_value, &block) - with_config_option(:view_component_path, new_value, &block) + with_generate_option(:view_component_paths, [new_value], &block) end def with_custom_component_parent_class(new_value, &block) - with_config_option(:component_parent_class, new_value, &block) + with_generate_option(:component_parent_class, new_value, &block) end def with_application_component_class @@ -139,6 +143,16 @@ def with_new_cache ViewComponent::CompileCache.cache = old_cache end +def with_default_preview_layout(new_value, &block) + old_value = Rails.application.config.view_component.previews.default_layout + Rails.application.config.view_component.previews.default_layout = new_value + app.reloader.reload! + yield +ensure + Rails.application.config.view_component.previews.default_layout = old_value + app.reloader.reload! +end + def without_template_annotations(&block) if ActionView::Base.respond_to?(:annotate_rendered_view_with_filenames) old_value = ActionView::Base.annotate_rendered_view_with_filenames @@ -165,10 +179,6 @@ def modify_file(file, content) end end -def with_default_preview_layout(layout, &block) - with_config_option(:default_preview_layout, layout, &block) -end - def with_compiler_development_mode(mode) previous_mode = ViewComponent::Compiler.development_mode ViewComponent::Compiler.development_mode = mode