view_component 2.35.0 → 2.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/controllers/view_components_controller.rb +1 -1
- data/docs/CHANGELOG.md +30 -0
- data/lib/view_component/base.rb +43 -13
- data/lib/view_component/collection.rb +4 -1
- data/lib/view_component/compiler.rb +40 -10
- data/lib/view_component/content_areas.rb +10 -3
- data/lib/view_component/engine.rb +13 -2
- data/lib/view_component/instrumentation.rb +5 -1
- data/lib/view_component/preview.rb +5 -1
- data/lib/view_component/slot_v2.rb +7 -1
- data/lib/view_component/slotable_v2.rb +40 -7
- data/lib/view_component/test_helpers.rb +7 -1
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component/with_content_helper.rb +4 -1
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 74a9bb65a4c364b86c92249805629eb7dc5d7dad010e6743a461cd668d6897f3
         | 
| 4 | 
            +
              data.tar.gz: ab5bf1fa266b729faebf8bed8b8884300a8fca4462d4ab8a0c6a0dce32153a26
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: eae497c864c6005b6ec9cea9073d2659b5378e0f0ebde82397a7e8a09e834525293f6b1a25a615198a28ae9ccfc077505c0bdb35def7cbea69aeaa3472dba7a3
         | 
| 7 | 
            +
              data.tar.gz: b11a86583fb7886828690d0d9d57ec9db3bf0f99b40f5cc0de5bb8fb6ca54ffd8e6fb65e150cdd2865c8cd71d46e13e9d7adbd3bd846d5ba5996a0a3c4ebf7d0
         | 
| @@ -56,7 +56,7 @@ class ViewComponentsController < Rails::ApplicationController # :nodoc: | |
| 56 56 | 
             
                if preview
         | 
| 57 57 | 
             
                  @preview = ViewComponent::Preview.find(preview)
         | 
| 58 58 | 
             
                else
         | 
| 59 | 
            -
                  raise AbstractController::ActionNotFound, "Component preview '#{params[:path]}' not found"
         | 
| 59 | 
            +
                  raise AbstractController::ActionNotFound, "Component preview '#{params[:path]}' not found."
         | 
| 60 60 | 
             
                end
         | 
| 61 61 | 
             
              end
         | 
| 62 62 |  | 
    
        data/docs/CHANGELOG.md
    CHANGED
    
    | @@ -7,6 +7,36 @@ title: Changelog | |
| 7 7 |  | 
| 8 8 | 
             
            ## main
         | 
| 9 9 |  | 
| 10 | 
            +
            ## 2.36.0
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            * Add `slot_type` helper method.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                *Jon Palmer*
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            * Add test case for rendering a ViewComponent with slots in a controller.
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                *Simon Fish*
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            * Add example ViewComponent to documentation landing page.
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                *Joel Hawksley*
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            * Set maximum line length to 120.
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                *Joel Hawksley*
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            * Setting a collection slot with the plural setter (`component.items(array)` for `renders_many :items`)  returns the array of slots.
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                *Jon Palmer*
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            * Update error messages to be more descriptive and helpful.
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                *Joel Hawksley*
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            * Raise an error if the slot name for renders_many is :contents
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                *Simon Fish*
         | 
| 39 | 
            +
             | 
| 10 40 | 
             
            ## 2.35.0
         | 
| 11 41 |  | 
| 12 42 | 
             
            * Only load assets for Preview source highlighting if previews are enabled.
         | 
    
        data/lib/view_component/base.rb
    CHANGED
    
    | @@ -86,7 +86,11 @@ module ViewComponent | |
| 86 86 | 
             
                  @current_template = self
         | 
| 87 87 |  | 
| 88 88 | 
             
                  if block && defined?(@__vc_content_set_by_with_content)
         | 
| 89 | 
            -
                    raise ArgumentError.new( | 
| 89 | 
            +
                    raise ArgumentError.new(
         | 
| 90 | 
            +
                      "It looks like a block was provided after calling `with_content` on #{self.class.name}, " \
         | 
| 91 | 
            +
                      "which means that ViewComponent doesn't know which content to use.\n\n" \
         | 
| 92 | 
            +
                      "To fix this issue, use either `with_content` or a block."
         | 
| 93 | 
            +
                    )
         | 
| 90 94 | 
             
                  end
         | 
| 91 95 |  | 
| 92 96 | 
             
                  @__vc_content_evaluated = false
         | 
| @@ -110,7 +114,8 @@ module ViewComponent | |
| 110 114 | 
             
                  ""
         | 
| 111 115 | 
             
                end
         | 
| 112 116 |  | 
| 113 | 
            -
                # Called before rendering the component. Override to perform operations that | 
| 117 | 
            +
                # Called before rendering the component. Override to perform operations that
         | 
| 118 | 
            +
                # depend on having access to the view context, such as helpers.
         | 
| 114 119 | 
             
                #
         | 
| 115 120 | 
             
                # @return [void]
         | 
| 116 121 | 
             
                def before_render
         | 
| @@ -150,20 +155,40 @@ module ViewComponent | |
| 150 155 | 
             
                  end
         | 
| 151 156 | 
             
                end
         | 
| 152 157 |  | 
| 153 | 
            -
                # The current controller. Use sparingly as doing so introduces coupling | 
| 158 | 
            +
                # The current controller. Use sparingly as doing so introduces coupling
         | 
| 159 | 
            +
                # that inhibits encapsulation & reuse, often making testing difficult.
         | 
| 154 160 | 
             
                #
         | 
| 155 161 | 
             
                # @return [ActionController::Base]
         | 
| 156 162 | 
             
                def controller
         | 
| 157 | 
            -
                   | 
| 163 | 
            +
                  if view_context.nil?
         | 
| 164 | 
            +
                    raise(
         | 
| 165 | 
            +
                      ViewContextCalledBeforeRenderError,
         | 
| 166 | 
            +
                      "`#controller` cannot be used during initialization, as it depends " \
         | 
| 167 | 
            +
                      "on the view context that only exists once a ViewComponent is passed to " \
         | 
| 168 | 
            +
                      "the Rails render pipeline.\n\n" \
         | 
| 169 | 
            +
                      "It's sometimes possible to fix this issue by moving code dependent on " \
         | 
| 170 | 
            +
                      "`#controller` to a `#before_render` method: https://viewcomponent.org/api.html#before_render--void."
         | 
| 171 | 
            +
                    )
         | 
| 172 | 
            +
                  end
         | 
| 158 173 |  | 
| 159 174 | 
             
                  @__vc_controller ||= view_context.controller
         | 
| 160 175 | 
             
                end
         | 
| 161 176 |  | 
| 162 | 
            -
                # A proxy through which to access helpers. Use sparingly as doing so introduces | 
| 177 | 
            +
                # A proxy through which to access helpers. Use sparingly as doing so introduces
         | 
| 178 | 
            +
                # coupling that inhibits encapsulation & reuse, often making testing difficult.
         | 
| 163 179 | 
             
                #
         | 
| 164 180 | 
             
                # @return [ActionView::Base]
         | 
| 165 181 | 
             
                def helpers
         | 
| 166 | 
            -
                   | 
| 182 | 
            +
                  if view_context.nil?
         | 
| 183 | 
            +
                    raise(
         | 
| 184 | 
            +
                      ViewContextCalledBeforeRenderError,
         | 
| 185 | 
            +
                      "`#helpers` cannot be used during initialization, as it depends " \
         | 
| 186 | 
            +
                      "on the view context that only exists once a ViewComponent is passed to " \
         | 
| 187 | 
            +
                      "the Rails render pipeline.\n\n" \
         | 
| 188 | 
            +
                      "It's sometimes possible to fix this issue by moving code dependent on " \
         | 
| 189 | 
            +
                      "`#helpers` to a `#before_render` method: https://viewcomponent.org/api.html#before_render--void."
         | 
| 190 | 
            +
                    )
         | 
| 191 | 
            +
                  end
         | 
| 167 192 |  | 
| 168 193 | 
             
                  @__vc_helpers ||= controller.view_context
         | 
| 169 194 | 
             
                end
         | 
| @@ -201,7 +226,8 @@ module ViewComponent | |
| 201 226 | 
             
                  self
         | 
| 202 227 | 
             
                end
         | 
| 203 228 |  | 
| 204 | 
            -
                # The current request. Use sparingly as doing so introduces coupling that | 
| 229 | 
            +
                # The current request. Use sparingly as doing so introduces coupling that
         | 
| 230 | 
            +
                # inhibits encapsulation & reuse, often making testing difficult.
         | 
| 205 231 | 
             
                #
         | 
| 206 232 | 
             
                # @return [ActionDispatch::Request]
         | 
| 207 233 | 
             
                def request
         | 
| @@ -409,13 +435,18 @@ module ViewComponent | |
| 409 435 | 
             
                    # the component.
         | 
| 410 436 | 
             
                    if initialize_parameters.empty?
         | 
| 411 437 | 
             
                      raise ArgumentError.new(
         | 
| 412 | 
            -
                        "#{self} initializer is empty or invalid."
         | 
| 438 | 
            +
                        "The #{self} initializer is empty or invalid." \
         | 
| 439 | 
            +
                        "It must accept the parameter `#{parameter}` to render it as a collection.\n\n" \
         | 
| 440 | 
            +
                        "To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
         | 
| 441 | 
            +
                        "See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
         | 
| 413 442 | 
             
                      )
         | 
| 414 443 | 
             
                    end
         | 
| 415 444 |  | 
| 416 445 | 
             
                    raise ArgumentError.new(
         | 
| 417 | 
            -
                      "#{self}  | 
| 418 | 
            -
                      " | 
| 446 | 
            +
                      "The initializer for #{self} does not accept the parameter `#{parameter}`, " \
         | 
| 447 | 
            +
                      "which is required in order to render it as a collection.\n\n" \
         | 
| 448 | 
            +
                      "To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
         | 
| 449 | 
            +
                      "See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
         | 
| 419 450 | 
             
                    )
         | 
| 420 451 | 
             
                  end
         | 
| 421 452 |  | 
| @@ -427,9 +458,8 @@ module ViewComponent | |
| 427 458 | 
             
                    return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
         | 
| 428 459 |  | 
| 429 460 | 
             
                    raise ViewComponent::ComponentError.new(
         | 
| 430 | 
            -
                      "#{self} initializer cannot  | 
| 431 | 
            -
                      " | 
| 432 | 
            -
                      "public ViewComponent method."
         | 
| 461 | 
            +
                      "#{self} initializer cannot accept the parameter `#{RESERVED_PARAMETER}`, as it will override a " \
         | 
| 462 | 
            +
                      "public ViewComponent method. To fix this issue, rename the parameter."
         | 
| 433 463 | 
             
                    )
         | 
| 434 464 | 
             
                  end
         | 
| 435 465 |  | 
| @@ -32,7 +32,10 @@ module ViewComponent | |
| 32 32 | 
             
                  if object.respond_to?(:to_ary)
         | 
| 33 33 | 
             
                    object.to_ary
         | 
| 34 34 | 
             
                  else
         | 
| 35 | 
            -
                    raise ArgumentError.new( | 
| 35 | 
            +
                    raise ArgumentError.new(
         | 
| 36 | 
            +
                      "The value of the first argument passed to `with_collection` isn't a valid collection. " \
         | 
| 37 | 
            +
                      "Make sure it responds to `to_ary`."
         | 
| 38 | 
            +
                    )
         | 
| 36 39 | 
             
                  end
         | 
| 37 40 | 
             
                end
         | 
| 38 41 |  | 
| @@ -16,7 +16,10 @@ module ViewComponent | |
| 16 16 | 
             
                  subclass_instance_methods = component_class.instance_methods(false)
         | 
| 17 17 |  | 
| 18 18 | 
             
                  if subclass_instance_methods.include?(:with_content) && raise_errors
         | 
| 19 | 
            -
                    raise ViewComponent::ComponentError.new( | 
| 19 | 
            +
                    raise ViewComponent::ComponentError.new(
         | 
| 20 | 
            +
                      "#{component_class} implements a reserved method, `#with_content`.\n\n" \
         | 
| 21 | 
            +
                      "To fix this issue, change the name of the method."
         | 
| 22 | 
            +
                    )
         | 
| 20 23 | 
             
                  end
         | 
| 21 24 |  | 
| 22 25 | 
             
                  if template_errors.present?
         | 
| @@ -27,7 +30,8 @@ module ViewComponent | |
| 27 30 |  | 
| 28 31 | 
             
                  if subclass_instance_methods.include?(:before_render_check)
         | 
| 29 32 | 
             
                    ActiveSupport::Deprecation.warn(
         | 
| 30 | 
            -
                      " | 
| 33 | 
            +
                      "`#before_render_check` will be removed in v3.0.0.\n\n" \
         | 
| 34 | 
            +
                      "To fix this issue, use `#before_render` instead."
         | 
| 31 35 | 
             
                    )
         | 
| 32 36 | 
             
                  end
         | 
| 33 37 |  | 
| @@ -40,7 +44,10 @@ module ViewComponent | |
| 40 44 | 
             
                    # Remove existing compiled template methods,
         | 
| 41 45 | 
             
                    # as Ruby warns when redefining a method.
         | 
| 42 46 | 
             
                    method_name = call_method_name(template[:variant])
         | 
| 43 | 
            -
             | 
| 47 | 
            +
             | 
| 48 | 
            +
                    if component_class.instance_methods.include?(method_name.to_sym)
         | 
| 49 | 
            +
                      component_class.send(:undef_method, method_name.to_sym)
         | 
| 50 | 
            +
                    end
         | 
| 44 51 |  | 
| 45 52 | 
             
                    component_class.class_eval <<-RUBY, template[:path], -1
         | 
| 46 53 | 
             
                      def #{method_name}
         | 
| @@ -62,7 +69,9 @@ module ViewComponent | |
| 62 69 | 
             
                attr_reader :component_class
         | 
| 63 70 |  | 
| 64 71 | 
             
                def define_render_template_for
         | 
| 65 | 
            -
                   | 
| 72 | 
            +
                  if component_class.instance_methods.include?(:render_template_for)
         | 
| 73 | 
            +
                    component_class.send(:undef_method, :render_template_for)
         | 
| 74 | 
            +
                  end
         | 
| 66 75 |  | 
| 67 76 | 
             
                  variant_elsifs = variants.compact.uniq.map do |variant|
         | 
| 68 77 | 
             
                    "elsif variant.to_sym == :#{variant}\n    #{call_method_name(variant)}"
         | 
| @@ -90,7 +99,9 @@ module ViewComponent | |
| 90 99 | 
             
                      end
         | 
| 91 100 |  | 
| 92 101 | 
             
                      if templates.count { |template| template[:variant].nil? } > 1
         | 
| 93 | 
            -
                        errors << | 
| 102 | 
            +
                        errors <<
         | 
| 103 | 
            +
                          "More than one template found for #{component_class}. " \
         | 
| 104 | 
            +
                          "There can only be one default template file per component."
         | 
| 94 105 | 
             
                      end
         | 
| 95 106 |  | 
| 96 107 | 
             
                      invalid_variants =
         | 
| @@ -101,11 +112,16 @@ module ViewComponent | |
| 101 112 | 
             
                        sort
         | 
| 102 113 |  | 
| 103 114 | 
             
                      unless invalid_variants.empty?
         | 
| 104 | 
            -
                        errors << | 
| 115 | 
            +
                        errors <<
         | 
| 116 | 
            +
                          "More than one template found for #{'variant'.pluralize(invalid_variants.count)} " \
         | 
| 117 | 
            +
                          "#{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{component_class}. " \
         | 
| 118 | 
            +
                          "There can only be one template file per variant."
         | 
| 105 119 | 
             
                      end
         | 
| 106 120 |  | 
| 107 121 | 
             
                      if templates.find { |template| template[:variant].nil? } && inline_calls_defined_on_self.include?(:call)
         | 
| 108 | 
            -
                        errors << | 
| 122 | 
            +
                        errors <<
         | 
| 123 | 
            +
                          "Template file and inline render method found for #{component_class}. " \
         | 
| 124 | 
            +
                          "There can only be a template file or inline render method per component."
         | 
| 109 125 | 
             
                      end
         | 
| 110 126 |  | 
| 111 127 | 
             
                      duplicate_template_file_and_inline_variant_calls =
         | 
| @@ -114,7 +130,12 @@ module ViewComponent | |
| 114 130 | 
             
                      unless duplicate_template_file_and_inline_variant_calls.empty?
         | 
| 115 131 | 
             
                        count = duplicate_template_file_and_inline_variant_calls.count
         | 
| 116 132 |  | 
| 117 | 
            -
                        errors << | 
| 133 | 
            +
                        errors <<
         | 
| 134 | 
            +
                          "Template #{'file'.pluralize(count)} and inline render #{'method'.pluralize(count)} " \
         | 
| 135 | 
            +
                          "found for #{'variant'.pluralize(count)} " \
         | 
| 136 | 
            +
                          "#{duplicate_template_file_and_inline_variant_calls.map { |v| "'#{v}'" }.to_sentence} " \
         | 
| 137 | 
            +
                          "in #{component_class}. " \
         | 
| 138 | 
            +
                          "There can only be a template file or inline render method per variant."
         | 
| 118 139 | 
             
                      end
         | 
| 119 140 |  | 
| 120 141 | 
             
                      errors
         | 
| @@ -143,7 +164,10 @@ module ViewComponent | |
| 143 164 | 
             
                      # Fetch only ViewComponent ancestor classes to limit the scope of
         | 
| 144 165 | 
             
                      # finding inline calls
         | 
| 145 166 | 
             
                      view_component_ancestors =
         | 
| 146 | 
            -
                         | 
| 167 | 
            +
                        (
         | 
| 168 | 
            +
                          component_class.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } -
         | 
| 169 | 
            +
                          component_class.included_modules
         | 
| 170 | 
            +
                        )
         | 
| 147 171 |  | 
| 148 172 | 
             
                      view_component_ancestors.flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call/) }.uniq
         | 
| 149 173 | 
             
                    end
         | 
| @@ -172,7 +196,13 @@ module ViewComponent | |
| 172 196 | 
             
                  if handler.method(:call).parameters.length > 1
         | 
| 173 197 | 
             
                    handler.call(component_class, template)
         | 
| 174 198 | 
             
                  else
         | 
| 175 | 
            -
                    handler.call( | 
| 199 | 
            +
                    handler.call(
         | 
| 200 | 
            +
                      OpenStruct.new(
         | 
| 201 | 
            +
                        source: template,
         | 
| 202 | 
            +
                        identifier: component_class.identifier,
         | 
| 203 | 
            +
                        type: component_class.type
         | 
| 204 | 
            +
                      )
         | 
| 205 | 
            +
                    )
         | 
| 176 206 | 
             
                  end
         | 
| 177 207 | 
             
                end
         | 
| 178 208 |  | 
| @@ -14,7 +14,11 @@ module ViewComponent | |
| 14 14 | 
             
                # @private
         | 
| 15 15 | 
             
                def with(area, content = nil, &block)
         | 
| 16 16 | 
             
                  unless content_areas.include?(area)
         | 
| 17 | 
            -
                    raise ArgumentError.new | 
| 17 | 
            +
                    raise ArgumentError.new(
         | 
| 18 | 
            +
                      "Unknown content_area '#{area}' for #{self} - expected one of '#{content_areas}'.\n\n" \
         | 
| 19 | 
            +
                      "To fix this issue, add `with_content_area :#{area}` to #{self} or reference " \
         | 
| 20 | 
            +
                      "a valid content area."
         | 
| 21 | 
            +
                    )
         | 
| 18 22 | 
             
                  end
         | 
| 19 23 |  | 
| 20 24 | 
             
                  if block_given?
         | 
| @@ -28,12 +32,15 @@ module ViewComponent | |
| 28 32 | 
             
                class_methods do
         | 
| 29 33 | 
             
                  def with_content_areas(*areas)
         | 
| 30 34 | 
             
                    ActiveSupport::Deprecation.warn(
         | 
| 31 | 
            -
                      "`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n" \
         | 
| 35 | 
            +
                      "`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n\n" \
         | 
| 32 36 | 
             
                      "Use slots (https://viewcomponent.org/guide/slots.html) instead."
         | 
| 33 37 | 
             
                    )
         | 
| 34 38 |  | 
| 35 39 | 
             
                    if areas.include?(:content)
         | 
| 36 | 
            -
                      raise ArgumentError.new | 
| 40 | 
            +
                      raise ArgumentError.new(
         | 
| 41 | 
            +
                        "#{self} defines a content area called :content, which is a reserved name. \n\n" \
         | 
| 42 | 
            +
                        "To fix this issue, use another name, such as `:body`."
         | 
| 43 | 
            +
                      )
         | 
| 37 44 | 
             
                    end
         | 
| 38 45 |  | 
| 39 46 | 
             
                    areas.each do |area|
         | 
| @@ -110,8 +110,19 @@ module ViewComponent | |
| 110 110 | 
             
                    app.routes.prepend do
         | 
| 111 111 | 
             
                      preview_controller = options.preview_controller.sub(/Controller$/, "").underscore
         | 
| 112 112 |  | 
| 113 | 
            -
                      get | 
| 114 | 
            -
             | 
| 113 | 
            +
                      get(
         | 
| 114 | 
            +
                        options.preview_route,
         | 
| 115 | 
            +
                        to: "#{preview_controller}#index",
         | 
| 116 | 
            +
                        as: :preview_view_components,
         | 
| 117 | 
            +
                        internal: true
         | 
| 118 | 
            +
                      )
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                      get(
         | 
| 121 | 
            +
                        "#{options.preview_route}/*path",
         | 
| 122 | 
            +
                        to: "#{preview_controller}#previews",
         | 
| 123 | 
            +
                        as: :preview_view_component,
         | 
| 124 | 
            +
                        internal: true
         | 
| 125 | 
            +
                      )
         | 
| 115 126 | 
             
                    end
         | 
| 116 127 | 
             
                  end
         | 
| 117 128 |  | 
| @@ -9,7 +9,11 @@ module ViewComponent # :nodoc: | |
| 9 9 | 
             
                end
         | 
| 10 10 |  | 
| 11 11 | 
             
                def render_in(view_context, &block)
         | 
| 12 | 
            -
                  ActiveSupport::Notifications.instrument( | 
| 12 | 
            +
                  ActiveSupport::Notifications.instrument(
         | 
| 13 | 
            +
                    "!render.view_component",
         | 
| 14 | 
            +
                    name: self.class.name,
         | 
| 15 | 
            +
                    identifier: self.class.identifier
         | 
| 16 | 
            +
                  ) do
         | 
| 13 17 | 
             
                    super(view_context, &block)
         | 
| 14 18 | 
             
                  end
         | 
| 15 19 | 
             
                end
         | 
| @@ -77,7 +77,11 @@ module ViewComponent # :nodoc: | |
| 77 77 | 
             
                      end
         | 
| 78 78 |  | 
| 79 79 | 
             
                    if preview_path.nil?
         | 
| 80 | 
            -
                      raise | 
| 80 | 
            +
                      raise(
         | 
| 81 | 
            +
                        PreviewTemplateError,
         | 
| 82 | 
            +
                        "A preview template for example #{example} does not exist.\n\n" \
         | 
| 83 | 
            +
                        "To fix this issue, create a template for the example."
         | 
| 84 | 
            +
                      )
         | 
| 81 85 | 
             
                    end
         | 
| 82 86 |  | 
| 83 87 | 
             
                    path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
         | 
| @@ -30,7 +30,13 @@ module ViewComponent | |
| 30 30 |  | 
| 31 31 | 
             
                  view_context = @parent.send(:view_context)
         | 
| 32 32 |  | 
| 33 | 
            -
                   | 
| 33 | 
            +
                  if defined?(@__vc_content_block) && defined?(@__vc_content_set_by_with_content)
         | 
| 34 | 
            +
                    raise ArgumentError.new(
         | 
| 35 | 
            +
                      "It looks like a block was provided after calling `with_content` on #{self.class.name}, " \
         | 
| 36 | 
            +
                      "which means that ViewComponent doesn't know which content to use.\n\n" \
         | 
| 37 | 
            +
                      "To fix this issue, use either `with_content` or a block."
         | 
| 38 | 
            +
                    )
         | 
| 39 | 
            +
                  end
         | 
| 34 40 |  | 
| 35 41 | 
             
                  @content =
         | 
| 36 42 | 
             
                    if defined?(@__vc_component_instance)
         | 
| @@ -65,7 +65,7 @@ module ViewComponent | |
| 65 65 | 
             
                  #     <% end %>
         | 
| 66 66 | 
             
                  #   <% end %>
         | 
| 67 67 | 
             
                  def renders_one(slot_name, callable = nil)
         | 
| 68 | 
            -
                     | 
| 68 | 
            +
                    validate_singular_slot_name(slot_name)
         | 
| 69 69 |  | 
| 70 70 | 
             
                    define_method slot_name do |*args, **kwargs, &block|
         | 
| 71 71 | 
             
                      if args.empty? && kwargs.empty? && block.nil?
         | 
| @@ -116,7 +116,7 @@ module ViewComponent | |
| 116 116 | 
             
                  #     <% end %>
         | 
| 117 117 | 
             
                  #   <% end %>
         | 
| 118 118 | 
             
                  def renders_many(slot_name, callable = nil)
         | 
| 119 | 
            -
                     | 
| 119 | 
            +
                    validate_plural_slot_name(slot_name)
         | 
| 120 120 |  | 
| 121 121 | 
             
                    singular_name = ActiveSupport::Inflector.singularize(slot_name)
         | 
| 122 122 |  | 
| @@ -133,7 +133,7 @@ module ViewComponent | |
| 133 133 | 
             
                      if collection_args.nil? && block.nil?
         | 
| 134 134 | 
             
                        get_slot(slot_name)
         | 
| 135 135 | 
             
                      else
         | 
| 136 | 
            -
                        collection_args. | 
| 136 | 
            +
                        collection_args.map do |args|
         | 
| 137 137 | 
             
                          set_slot(slot_name, **args, &block)
         | 
| 138 138 | 
             
                        end
         | 
| 139 139 | 
             
                      end
         | 
| @@ -142,6 +142,17 @@ module ViewComponent | |
| 142 142 | 
             
                    register_slot(slot_name, collection: true, callable: callable)
         | 
| 143 143 | 
             
                  end
         | 
| 144 144 |  | 
| 145 | 
            +
                  def slot_type(slot_name)
         | 
| 146 | 
            +
                    registered_slot = registered_slots[slot_name]
         | 
| 147 | 
            +
                    if registered_slot
         | 
| 148 | 
            +
                      registered_slot[:collection] ? :collection : :single
         | 
| 149 | 
            +
                    else
         | 
| 150 | 
            +
                      plural_slot_name = ActiveSupport::Inflector.pluralize(slot_name).to_sym
         | 
| 151 | 
            +
                      plural_registered_slot = registered_slots[plural_slot_name]
         | 
| 152 | 
            +
                      plural_registered_slot&.fetch(:collection) ? :collection_item : nil
         | 
| 153 | 
            +
                    end
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
             | 
| 145 156 | 
             
                  # Clone slot configuration into child class
         | 
| 146 157 | 
             
                  # see #test_slots_pollution
         | 
| 147 158 | 
             
                  def inherited(child)
         | 
| @@ -174,14 +185,35 @@ module ViewComponent | |
| 174 185 | 
             
                    self.registered_slots[slot_name] = slot
         | 
| 175 186 | 
             
                  end
         | 
| 176 187 |  | 
| 177 | 
            -
                  def  | 
| 188 | 
            +
                  def validate_plural_slot_name(slot_name)
         | 
| 189 | 
            +
                    if slot_name.to_sym == :contents
         | 
| 190 | 
            +
                      raise ArgumentError.new(
         | 
| 191 | 
            +
                        "#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
         | 
| 192 | 
            +
                        "To fix this issue, choose a different name."
         | 
| 193 | 
            +
                      )
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                    raise_if_slot_registered(slot_name)
         | 
| 197 | 
            +
                  end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                  def validate_singular_slot_name(slot_name)
         | 
| 178 200 | 
             
                    if slot_name.to_sym == :content
         | 
| 179 | 
            -
                      raise ArgumentError.new( | 
| 201 | 
            +
                      raise ArgumentError.new(
         | 
| 202 | 
            +
                        "#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
         | 
| 203 | 
            +
                        "To fix this issue, choose a different name."
         | 
| 204 | 
            +
                      )
         | 
| 180 205 | 
             
                    end
         | 
| 181 206 |  | 
| 207 | 
            +
                    raise_if_slot_registered(slot_name)
         | 
| 208 | 
            +
                  end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                  def raise_if_slot_registered(slot_name)
         | 
| 182 211 | 
             
                    if self.registered_slots.key?(slot_name)
         | 
| 183 212 | 
             
                      # TODO remove? This breaks overriding slots when slots are inherited
         | 
| 184 | 
            -
                      raise ArgumentError.new( | 
| 213 | 
            +
                      raise ArgumentError.new(
         | 
| 214 | 
            +
                        "#{self} declares the #{slot_name} slot multiple times.\n\n" \
         | 
| 215 | 
            +
                        "To fix this issue, choose a different slot name."
         | 
| 216 | 
            +
                      )
         | 
| 185 217 | 
             
                    end
         | 
| 186 218 | 
             
                  end
         | 
| 187 219 | 
             
                end
         | 
| @@ -224,7 +256,8 @@ module ViewComponent | |
| 224 256 | 
             
                    slot.__vc_component_instance = slot_definition[:renderable].new(*args, **kwargs)
         | 
| 225 257 | 
             
                  # If class name as a string
         | 
| 226 258 | 
             
                  elsif slot_definition[:renderable_class_name]
         | 
| 227 | 
            -
                    slot.__vc_component_instance = | 
| 259 | 
            +
                    slot.__vc_component_instance =
         | 
| 260 | 
            +
                      self.class.const_get(slot_definition[:renderable_class_name]).new(*args, **kwargs)
         | 
| 228 261 | 
             
                  # If passed a lambda
         | 
| 229 262 | 
             
                  elsif slot_definition[:renderable_function]
         | 
| 230 263 | 
             
                    # Use `bind(self)` to ensure lambda is executed in the context of the
         | 
| @@ -17,7 +17,13 @@ module ViewComponent | |
| 17 17 | 
             
                  # We don't have a test case for running an application without capybara installed.
         | 
| 18 18 | 
             
                  # It's probably fine to leave this without coverage.
         | 
| 19 19 | 
             
                  # :nocov:
         | 
| 20 | 
            -
                   | 
| 20 | 
            +
                  if ENV["DEBUG"]
         | 
| 21 | 
            +
                    warn(
         | 
| 22 | 
            +
                      "WARNING in `ViewComponent::TestHelpers`: You must add `capybara` " \
         | 
| 23 | 
            +
                      "to your Gemfile to use Capybara assertions."
         | 
| 24 | 
            +
                    )
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 21 27 | 
             
                  # :nocov:
         | 
| 22 28 | 
             
                end
         | 
| 23 29 |  | 
| @@ -4,7 +4,10 @@ module ViewComponent | |
| 4 4 | 
             
              module WithContentHelper
         | 
| 5 5 | 
             
                def with_content(value)
         | 
| 6 6 | 
             
                  if value.nil?
         | 
| 7 | 
            -
                    raise ArgumentError.new( | 
| 7 | 
            +
                    raise ArgumentError.new(
         | 
| 8 | 
            +
                      "No content provided to `#with_content` for #{self}.\n\n" \
         | 
| 9 | 
            +
                      "To fix this issue, pass a value."
         | 
| 10 | 
            +
                    )
         | 
| 8 11 | 
             
                  else
         | 
| 9 12 | 
             
                    @__vc_content_set_by_with_content = value
         | 
| 10 13 | 
             
                  end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: view_component
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2. | 
| 4 | 
            +
              version: 2.36.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - GitHub Open Source
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2021-07- | 
| 11 | 
            +
            date: 2021-07-28 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: activesupport
         |