view_component 3.23.2 → 4.0.2

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/view_component/preview_actions.rb +11 -14
  3. data/app/controllers/view_components_system_test_controller.rb +15 -20
  4. data/app/views/test_mailer/test_asset_email.html.erb +1 -0
  5. data/app/views/test_mailer/test_url_email.html.erb +1 -0
  6. data/app/views/view_components/preview.html.erb +1 -9
  7. data/docs/CHANGELOG.md +422 -0
  8. data/lib/{rails/generators → generators/view_component}/abstract_generator.rb +2 -2
  9. data/lib/{rails/generators → generators/view_component}/component/component_generator.rb +16 -3
  10. data/lib/{rails/generators → generators/view_component}/component/templates/component.rb.tt +6 -1
  11. data/lib/{rails/generators/erb/component_generator.rb → generators/view_component/erb/erb_generator.rb} +4 -3
  12. data/lib/{rails/generators/haml/component_generator.rb → generators/view_component/haml/haml_generator.rb} +3 -3
  13. data/lib/{rails/generators/locale/component_generator.rb → generators/view_component/locale/locale_generator.rb} +3 -3
  14. data/lib/{rails/generators/preview/component_generator.rb → generators/view_component/preview/preview_generator.rb} +3 -3
  15. data/lib/{rails/generators/rspec/component_generator.rb → generators/view_component/rspec/rspec_generator.rb} +3 -3
  16. data/lib/{rails/generators/slim/component_generator.rb → generators/view_component/slim/slim_generator.rb} +3 -3
  17. data/lib/{rails/generators/stimulus/component_generator.rb → generators/view_component/stimulus/stimulus_generator.rb} +3 -3
  18. data/lib/generators/view_component/tailwindcss/tailwindcss_generator.rb +11 -0
  19. data/lib/{rails/generators/test_unit/component_generator.rb → generators/view_component/test_unit/test_unit_generator.rb} +2 -2
  20. data/lib/view_component/base.rb +178 -158
  21. data/lib/view_component/collection.rb +19 -25
  22. data/lib/view_component/compiler.rb +52 -79
  23. data/lib/view_component/config.rb +51 -85
  24. data/lib/view_component/configurable.rb +1 -1
  25. data/lib/view_component/deprecation.rb +1 -1
  26. data/lib/view_component/engine.rb +37 -107
  27. data/lib/view_component/errors.rb +16 -34
  28. data/lib/view_component/inline_template.rb +3 -4
  29. data/lib/view_component/instrumentation.rb +4 -10
  30. data/lib/view_component/preview.rb +4 -11
  31. data/lib/view_component/request_details.rb +30 -0
  32. data/lib/view_component/slot.rb +6 -13
  33. data/lib/view_component/slotable.rb +82 -77
  34. data/lib/view_component/system_spec_helpers.rb +11 -0
  35. data/lib/view_component/system_test_helpers.rb +1 -2
  36. data/lib/view_component/template.rb +106 -83
  37. data/lib/view_component/test_helpers.rb +46 -43
  38. data/lib/view_component/translatable.rb +33 -32
  39. data/lib/view_component/version.rb +2 -2
  40. data/lib/view_component.rb +8 -6
  41. metadata +31 -559
  42. data/app/assets/vendor/prism.css +0 -4
  43. data/app/assets/vendor/prism.min.js +0 -12
  44. data/app/helpers/preview_helper.rb +0 -85
  45. data/app/views/view_components/_preview_source.html.erb +0 -17
  46. data/lib/rails/generators/tailwindcss/component_generator.rb +0 -11
  47. data/lib/view_component/capture_compatibility.rb +0 -44
  48. data/lib/view_component/component_error.rb +0 -6
  49. data/lib/view_component/rails/tasks/view_component.rake +0 -20
  50. data/lib/view_component/render_component_helper.rb +0 -10
  51. data/lib/view_component/render_component_to_string_helper.rb +0 -9
  52. data/lib/view_component/render_monkey_patch.rb +0 -13
  53. data/lib/view_component/render_to_string_monkey_patch.rb +0 -13
  54. data/lib/view_component/rendering_component_helper.rb +0 -9
  55. data/lib/view_component/rendering_monkey_patch.rb +0 -13
  56. data/lib/view_component/slotable_default.rb +0 -20
  57. data/lib/view_component/use_helpers.rb +0 -42
  58. /data/lib/{rails/generators → generators/view_component}/erb/templates/component.html.erb.tt +0 -0
  59. /data/lib/{rails/generators → generators/view_component}/haml/templates/component.html.haml.tt +0 -0
  60. /data/lib/{rails/generators → generators/view_component}/preview/templates/component_preview.rb.tt +0 -0
  61. /data/lib/{rails/generators → generators/view_component}/rspec/templates/component_spec.rb.tt +0 -0
  62. /data/lib/{rails/generators → generators/view_component}/slim/templates/component.html.slim.tt +0 -0
  63. /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.js.tt +0 -0
  64. /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.ts.tt +0 -0
  65. /data/lib/{rails/generators → generators/view_component}/tailwindcss/templates/component.html.erb.tt +0 -0
  66. /data/lib/{rails/generators → generators/view_component}/test_unit/templates/component_test.rb.tt +0 -0
@@ -38,6 +38,22 @@ module ViewComponent
38
38
  end
39
39
  end
40
40
 
41
+ class MissingTemplateError < StandardError
42
+ MESSAGE =
43
+ "No templates for COMPONENT match the request DETAIL.\n\n" \
44
+ "To fix this issue, provide a suitable template."
45
+
46
+ def initialize(component, request_detail)
47
+ detail = {
48
+ locale: request_detail.locale,
49
+ formats: request_detail.formats,
50
+ variants: request_detail.variants,
51
+ handlers: request_detail.handlers
52
+ }
53
+ super(MESSAGE.gsub("COMPONENT", component).gsub("DETAIL", detail.inspect))
54
+ end
55
+ end
56
+
41
57
  class DuplicateContentError < StandardError
42
58
  MESSAGE =
43
59
  "It looks like a block was provided after calling `with_content` on COMPONENT, " \
@@ -49,18 +65,6 @@ module ViewComponent
49
65
  end
50
66
  end
51
67
 
52
- class EmptyOrInvalidInitializerError < StandardError
53
- MESSAGE =
54
- "The COMPONENT initializer is empty or invalid. " \
55
- "It must accept the parameter `PARAMETER` to render it as a collection.\n\n" \
56
- "To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
57
- "See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."
58
-
59
- def initialize(klass_name, parameter)
60
- super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
61
- end
62
- end
63
-
64
68
  class MissingCollectionArgumentError < StandardError
65
69
  MESSAGE =
66
70
  "The initializer for COMPONENT doesn't accept the parameter `PARAMETER`, " \
@@ -202,28 +206,6 @@ module ViewComponent
202
206
  "`#controller` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
203
207
  end
204
208
 
205
- # :nocov:
206
- class NoMatchingTemplatesForPreviewError < StandardError
207
- MESSAGE = "Found 0 matches for templates for TEMPLATE_IDENTIFIER."
208
-
209
- def initialize(template_identifier)
210
- super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier))
211
- end
212
- end
213
-
214
- class MultipleMatchingTemplatesForPreviewError < StandardError
215
- MESSAGE = "Found multiple templates for TEMPLATE_IDENTIFIER."
216
-
217
- def initialize(template_identifier)
218
- super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier))
219
- end
220
- end
221
- # :nocov:
222
-
223
- class SystemTestControllerOnlyAllowedInTestError < BaseError
224
- MESSAGE = "ViewComponent SystemTest controller must only be called in a test environment for security reasons."
225
- end
226
-
227
209
  class SystemTestControllerNefariousPathError < BaseError
228
210
  MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory."
229
211
  end
@@ -32,23 +32,22 @@ module ViewComponent # :nodoc:
32
32
 
33
33
  @__vc_inline_template_defined = true
34
34
  end
35
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
36
35
 
37
36
  def respond_to_missing?(method, include_all = false)
38
37
  method.end_with?("_template") || super
39
38
  end
40
39
 
41
- def inline_template
40
+ def __vc_inline_template
42
41
  @__vc_inline_template if defined?(@__vc_inline_template)
43
42
  end
44
43
 
45
- def inline_template_language
44
+ def __vc_inline_template_language
46
45
  @__vc_inline_template_language if defined?(@__vc_inline_template_language)
47
46
  end
48
47
 
49
48
  def inherited(subclass)
50
49
  super
51
- subclass.instance_variable_set(:@__vc_inline_template_language, inline_template_language)
50
+ subclass.instance_variable_set(:@__vc_inline_template_language, __vc_inline_template_language)
52
51
  end
53
52
  end
54
53
  end
@@ -5,12 +5,14 @@ require "active_support/notifications"
5
5
  module ViewComponent # :nodoc:
6
6
  module Instrumentation
7
7
  def self.included(mod)
8
- mod.prepend(self) unless ancestors.include?(ViewComponent::Instrumentation)
8
+ mod.prepend(self) unless self <= ViewComponent::Instrumentation
9
9
  end
10
10
 
11
11
  def render_in(view_context, &block)
12
+ return super if !Rails.application.config.view_component.instrumentation_enabled.present?
13
+
12
14
  ActiveSupport::Notifications.instrument(
13
- notification_name,
15
+ "render.view_component",
14
16
  {
15
17
  name: self.class.name,
16
18
  identifier: self.class.identifier
@@ -19,13 +21,5 @@ module ViewComponent # :nodoc:
19
21
  super
20
22
  end
21
23
  end
22
-
23
- private
24
-
25
- def notification_name
26
- return "!render.view_component" if ViewComponent::Base.config.use_deprecated_instrumentation_name
27
-
28
- "render.view_component"
29
- end
30
24
  end
31
25
  end
@@ -30,12 +30,10 @@ module ViewComponent # :nodoc:
30
30
  }
31
31
  end
32
32
 
33
- alias_method :render_component, :render
34
-
35
33
  class << self
36
34
  # Returns all component preview classes.
37
35
  def all
38
- load_previews
36
+ __vc_load_previews
39
37
 
40
38
  descendants
41
39
  end
@@ -94,13 +92,8 @@ module ViewComponent # :nodoc:
94
92
  .sub(/\..*$/, "")
95
93
  end
96
94
 
97
- # Returns the method body for the example from the preview file.
98
- def preview_source(example)
99
- source = instance_method(example.to_sym).source.split("\n")
100
- source[1...(source.size - 1)].join("\n")
101
- end
102
-
103
- def load_previews
95
+ # @private
96
+ def __vc_load_previews
104
97
  Array(preview_paths).each do |preview_path|
105
98
  Dir["#{preview_path}/**/*preview.rb"].sort.each { |file| require_dependency file }
106
99
  end
@@ -109,7 +102,7 @@ module ViewComponent # :nodoc:
109
102
  private
110
103
 
111
104
  def preview_paths
112
- Base.preview_paths
105
+ Base.previews.paths
113
106
  end
114
107
  end
115
108
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ # LookupContext computes and encapsulates @details for each request
5
+ # so that it doesn't need to be recomputed on each partial render.
6
+ # This data is wrapped in ActionView::TemplateDetails::Requested and
7
+ # used by instances of ActionView::Resolver to choose which template
8
+ # best matches the request.
9
+ #
10
+ # ActionView considers this logic internal to template/partial resolution.
11
+ # We're exposing it to the compiler via `refine` so that ViewComponent
12
+ # can match Rails' template picking logic.
13
+ module RequestDetails
14
+ refine ActionView::LookupContext do
15
+ # Return an abstraction for matching and sorting available templates
16
+ # based on the current lookup context details.
17
+ #
18
+ # @return ActionView::TemplateDetails::Requested
19
+ # @see ActionView::LookupContext#detail_args_for
20
+ # @see ActionView::FileSystemResolver#_find_all
21
+ def vc_requested_details(user_details = {})
22
+ # The hash `user_details` would normally be the standard arguments that
23
+ # `render` accepts, but there's currently no mechanism for users to
24
+ # provide these when calling render on a ViewComponent.
25
+ details, cached = detail_args_for(user_details)
26
+ cached || ActionView::TemplateDetails::Requested.new(**details)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -58,13 +58,7 @@ module ViewComponent
58
58
  if defined?(@__vc_content_block)
59
59
  # render_in is faster than `parent.render`
60
60
  @__vc_component_instance.render_in(view_context) do |*args|
61
- return @__vc_content_block.call(*args) if @__vc_content_block&.source_location.nil?
62
-
63
- block_context = @__vc_content_block.binding.receiver
64
-
65
- if block_context.class < ActionView::Base
66
- block_context.capture(*args, &@__vc_content_block)
67
- else
61
+ @parent.with_original_virtual_path do
68
62
  @__vc_content_block.call(*args)
69
63
  end
70
64
  end
@@ -74,7 +68,9 @@ module ViewComponent
74
68
  elsif defined?(@__vc_content)
75
69
  @__vc_content
76
70
  elsif defined?(@__vc_content_block)
77
- view_context.capture(&@__vc_content_block)
71
+ @parent.with_original_virtual_path do
72
+ view_context.capture(&@__vc_content_block)
73
+ end
78
74
  elsif defined?(@__vc_content_set_by_with_content)
79
75
  @__vc_content_set_by_with_content
80
76
  end
@@ -101,15 +97,12 @@ module ViewComponent
101
97
  # end
102
98
  # end
103
99
  #
104
- def method_missing(symbol, *args, &block)
105
- @__vc_component_instance.public_send(symbol, *args, &block)
100
+ def method_missing(symbol, *args, **kwargs, &block)
101
+ @__vc_component_instance.public_send(symbol, *args, **kwargs, &block)
106
102
  end
107
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
108
103
 
109
104
  def html_safe?
110
- # :nocov:
111
105
  to_s.html_safe?
112
- # :nocov:
113
106
  end
114
107
 
115
108
  def respond_to_missing?(symbol, include_all = false)
@@ -12,12 +12,10 @@ module ViewComponent
12
12
  singular: %i[content render].freeze,
13
13
  plural: %i[contents renders].freeze
14
14
  }.freeze
15
+ private_constant :RESERVED_NAMES
15
16
 
16
- # Setup component slot state
17
17
  included do
18
- # Hash of registered Slots
19
- class_attribute :registered_slots
20
- self.registered_slots = {}
18
+ class_attribute :registered_slots, default: {}
21
19
  end
22
20
 
23
21
  class_methods do
@@ -75,26 +73,25 @@ module ViewComponent
75
73
  #
76
74
  # <%= render_inline(MyComponent.new.with_header_content("Foo")) %>
77
75
  def renders_one(slot_name, callable = nil)
78
- validate_singular_slot_name(slot_name)
76
+ __vc_validate_singular_slot_name(slot_name)
79
77
 
80
78
  if callable.is_a?(Hash) && callable.key?(:types)
81
- register_polymorphic_slot(slot_name, callable[:types], collection: false)
79
+ __vc_register_polymorphic_slot(slot_name, callable[:types], collection: false)
82
80
  else
83
- validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym)
81
+ __vc_validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym)
84
82
 
85
83
  setter_method_name = :"with_#{slot_name}"
86
84
 
87
- define_method setter_method_name do |*args, &block|
88
- set_slot(slot_name, nil, *args, &block)
85
+ define_method setter_method_name do |*args, **kwargs, &block|
86
+ __vc_set_slot(slot_name, nil, *args, **kwargs, &block)
89
87
  end
90
- ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
91
88
 
92
89
  self::GeneratedSlotMethods.define_method slot_name do
93
- get_slot(slot_name)
90
+ __vc_get_slot(slot_name)
94
91
  end
95
92
 
96
93
  self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
97
- get_slot(slot_name).present?
94
+ __vc_get_slot(slot_name).present?
98
95
  end
99
96
 
100
97
  define_method :"with_#{slot_name}_content" do |content|
@@ -103,7 +100,7 @@ module ViewComponent
103
100
  self
104
101
  end
105
102
 
106
- register_slot(slot_name, collection: false, callable: callable)
103
+ __vc_register_slot(slot_name, collection: false, callable: callable)
107
104
  end
108
105
  end
109
106
 
@@ -145,20 +142,19 @@ module ViewComponent
145
142
  # <% end %>
146
143
  # <% end %>
147
144
  def renders_many(slot_name, callable = nil)
148
- validate_plural_slot_name(slot_name)
145
+ __vc_validate_plural_slot_name(slot_name)
149
146
 
150
147
  if callable.is_a?(Hash) && callable.key?(:types)
151
- register_polymorphic_slot(slot_name, callable[:types], collection: true)
148
+ __vc_register_polymorphic_slot(slot_name, callable[:types], collection: true)
152
149
  else
153
150
  singular_name = ActiveSupport::Inflector.singularize(slot_name)
154
- validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym)
151
+ __vc_validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym)
155
152
 
156
153
  setter_method_name = :"with_#{singular_name}"
157
154
 
158
- define_method setter_method_name do |*args, &block|
159
- set_slot(slot_name, nil, *args, &block)
155
+ define_method setter_method_name do |*args, **kwargs, &block|
156
+ __vc_set_slot(slot_name, nil, *args, **kwargs, &block)
160
157
  end
161
- ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
162
158
 
163
159
  define_method :"with_#{singular_name}_content" do |content|
164
160
  send(setter_method_name) { content.to_s }
@@ -169,22 +165,22 @@ module ViewComponent
169
165
  define_method :"with_#{slot_name}" do |collection_args = nil, &block|
170
166
  collection_args.map do |args|
171
167
  if args.respond_to?(:to_hash)
172
- set_slot(slot_name, nil, **args, &block)
168
+ __vc_set_slot(slot_name, nil, **args, &block)
173
169
  else
174
- set_slot(slot_name, nil, *args, &block)
170
+ __vc_set_slot(slot_name, nil, *args, &block)
175
171
  end
176
172
  end
177
173
  end
178
174
 
179
175
  self::GeneratedSlotMethods.define_method slot_name do
180
- get_slot(slot_name)
176
+ __vc_get_slot(slot_name)
181
177
  end
182
178
 
183
179
  self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
184
- get_slot(slot_name).present?
180
+ __vc_get_slot(slot_name).present?
185
181
  end
186
182
 
187
- register_slot(slot_name, collection: true, callable: callable)
183
+ __vc_register_slot(slot_name, collection: true, callable: callable)
188
184
  end
189
185
  end
190
186
 
@@ -215,13 +211,30 @@ module ViewComponent
215
211
  super
216
212
  end
217
213
 
218
- def register_polymorphic_slot(slot_name, types, collection:)
214
+ # @private
215
+ # Called by the compiler, as instance methods are not defined when slots are first registered
216
+ def __vc_register_default_slots
217
+ registered_slots.each do |slot_name, config|
218
+ default_method_name = :"default_#{slot_name}"
219
+ config[:default_method] = instance_methods.find { |method_name| method_name == default_method_name }
220
+
221
+ registered_slots[slot_name] = config
222
+ end
223
+ end
224
+
225
+ private
226
+
227
+ def __vc_register_slot(slot_name, **kwargs)
228
+ registered_slots[slot_name] = __vc_define_slot(slot_name, **kwargs)
229
+ end
230
+
231
+ def __vc_register_polymorphic_slot(slot_name, types, collection:)
219
232
  self::GeneratedSlotMethods.define_method(slot_name) do
220
- get_slot(slot_name)
233
+ __vc_get_slot(slot_name)
221
234
  end
222
235
 
223
236
  self::GeneratedSlotMethods.define_method(:"#{slot_name}?") do
224
- get_slot(slot_name).present?
237
+ __vc_get_slot(slot_name).present?
225
238
  end
226
239
 
227
240
  renderable_hash = types.each_with_object({}) do |(poly_type, poly_attributes_or_callable), memo|
@@ -240,7 +253,7 @@ module ViewComponent
240
253
  "#{slot_name}_#{poly_type}"
241
254
  end
242
255
 
243
- memo[poly_type] = define_slot(
256
+ memo[poly_type] = __vc_define_slot(
244
257
  poly_slot_name, collection: collection, callable: poly_callable
245
258
  )
246
259
 
@@ -250,10 +263,9 @@ module ViewComponent
250
263
  raise AlreadyDefinedPolymorphicSlotSetterError.new(setter_method_name, poly_slot_name)
251
264
  end
252
265
 
253
- define_method(setter_method_name) do |*args, &block|
254
- set_polymorphic_slot(slot_name, poly_type, *args, &block)
266
+ define_method(setter_method_name) do |*args, **kwargs, &block|
267
+ __vc_set_polymorphic_slot(slot_name, poly_type, *args, **kwargs, &block)
255
268
  end
256
- ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
257
269
 
258
270
  define_method :"with_#{poly_slot_name}_content" do |content|
259
271
  send(setter_method_name) { content.to_s }
@@ -268,22 +280,7 @@ module ViewComponent
268
280
  }
269
281
  end
270
282
 
271
- # Called by the compiler, as instance methods are not defined when slots are first registered
272
- def register_default_slots
273
- registered_slots.each do |slot_name, config|
274
- config[:default_method] = instance_methods.find { |method_name| method_name == :"default_#{slot_name}" }
275
-
276
- registered_slots[slot_name] = config
277
- end
278
- end
279
-
280
- private
281
-
282
- def register_slot(slot_name, **kwargs)
283
- registered_slots[slot_name] = define_slot(slot_name, **kwargs)
284
- end
285
-
286
- def define_slot(slot_name, collection:, callable:)
283
+ def __vc_define_slot(slot_name, collection:, callable:)
287
284
  slot = {collection: collection}
288
285
  return slot unless callable
289
286
 
@@ -306,18 +303,18 @@ module ViewComponent
306
303
  slot
307
304
  end
308
305
 
309
- def validate_plural_slot_name(slot_name)
306
+ def __vc_validate_plural_slot_name(slot_name)
310
307
  if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
311
308
  raise ReservedPluralSlotNameError.new(name, slot_name)
312
309
  end
313
310
 
314
- raise_if_slot_name_uncountable(slot_name)
315
- raise_if_slot_conflicts_with_call(slot_name)
316
- raise_if_slot_ends_with_question_mark(slot_name)
317
- raise_if_slot_registered(slot_name)
311
+ __vc_raise_if_slot_name_uncountable(slot_name)
312
+ __vc_raise_if_slot_conflicts_with_call(slot_name)
313
+ __vc_raise_if_slot_ends_with_question_mark(slot_name)
314
+ __vc_raise_if_slot_registered(slot_name)
318
315
  end
319
316
 
320
- def validate_singular_slot_name(slot_name)
317
+ def __vc_validate_singular_slot_name(slot_name)
321
318
  if slot_name.to_sym == :content
322
319
  raise ContentSlotNameError.new(name)
323
320
  end
@@ -326,28 +323,28 @@ module ViewComponent
326
323
  raise ReservedSingularSlotNameError.new(name, slot_name)
327
324
  end
328
325
 
329
- raise_if_slot_conflicts_with_call(slot_name)
330
- raise_if_slot_ends_with_question_mark(slot_name)
331
- raise_if_slot_registered(slot_name)
326
+ __vc_raise_if_slot_conflicts_with_call(slot_name)
327
+ __vc_raise_if_slot_ends_with_question_mark(slot_name)
328
+ __vc_raise_if_slot_registered(slot_name)
332
329
  end
333
330
 
334
- def raise_if_slot_registered(slot_name)
331
+ def __vc_raise_if_slot_registered(slot_name)
335
332
  if registered_slots.key?(slot_name)
336
333
  raise RedefinedSlotError.new(name, slot_name)
337
334
  end
338
335
  end
339
336
 
340
- def raise_if_slot_ends_with_question_mark(slot_name)
337
+ def __vc_raise_if_slot_ends_with_question_mark(slot_name)
341
338
  raise SlotPredicateNameError.new(name, slot_name) if slot_name.to_s.end_with?("?")
342
339
  end
343
340
 
344
- def raise_if_slot_conflicts_with_call(slot_name)
341
+ def __vc_raise_if_slot_conflicts_with_call(slot_name)
345
342
  if slot_name.start_with?("call_")
346
343
  raise InvalidSlotNameError, "Slot cannot start with 'call_'. Please rename #{slot_name}"
347
344
  end
348
345
  end
349
346
 
350
- def raise_if_slot_name_uncountable(slot_name)
347
+ def __vc_raise_if_slot_name_uncountable(slot_name)
351
348
  slot_name = slot_name.to_s
352
349
  if slot_name.pluralize == slot_name.singularize
353
350
  raise UncountableSlotNameError.new(name, slot_name)
@@ -355,22 +352,32 @@ module ViewComponent
355
352
  end
356
353
  end
357
354
 
358
- def get_slot(slot_name)
359
- content unless content_evaluated? # ensure content is loaded so slots will be defined
360
-
361
- slot = self.class.registered_slots[slot_name]
355
+ def __vc_get_slot(slot_name)
362
356
  @__vc_set_slots ||= {}
357
+ content unless defined?(@__vc_content_evaluated) && @__vc_content_evaluated # ensure content is loaded so slots will be defined
363
358
 
364
- if @__vc_set_slots[slot_name]
365
- return @__vc_set_slots[slot_name]
366
- end
359
+ # If the slot is set, return it
360
+ return @__vc_set_slots[slot_name] if @__vc_set_slots[slot_name]
367
361
 
368
- if slot[:collection]
362
+ # If there is a default method for the slot, call it
363
+ if (default_method = registered_slots[slot_name][:default_method])
364
+ renderable_value = send(default_method)
365
+ slot = Slot.new(self)
366
+
367
+ if renderable_value.respond_to?(:render_in)
368
+ slot.__vc_component_instance = renderable_value
369
+ else
370
+ slot.__vc_content = renderable_value
371
+ end
372
+
373
+ slot
374
+ elsif self.class.registered_slots[slot_name][:collection]
375
+ # If empty slot is a collection, return an empty array
369
376
  []
370
377
  end
371
378
  end
372
379
 
373
- def set_slot(slot_name, slot_definition = nil, *args, &block)
380
+ def __vc_set_slot(slot_name, slot_definition = nil, *args, **kwargs, &block)
374
381
  slot_definition ||= self.class.registered_slots[slot_name]
375
382
  slot = Slot.new(self)
376
383
 
@@ -387,11 +394,11 @@ module ViewComponent
387
394
 
388
395
  # If class
389
396
  if slot_definition[:renderable]
390
- slot.__vc_component_instance = slot_definition[:renderable].new(*args)
397
+ slot.__vc_component_instance = slot_definition[:renderable].new(*args, **kwargs)
391
398
  # If class name as a string
392
399
  elsif slot_definition[:renderable_class_name]
393
400
  slot.__vc_component_instance =
394
- self.class.const_get(slot_definition[:renderable_class_name]).new(*args)
401
+ self.class.const_get(slot_definition[:renderable_class_name]).new(*args, **kwargs)
395
402
  # If passed a lambda
396
403
  elsif slot_definition[:renderable_function]
397
404
  # Use `bind(self)` to ensure lambda is executed in the context of the
@@ -400,11 +407,11 @@ module ViewComponent
400
407
  renderable_function = slot_definition[:renderable_function].bind(self)
401
408
  renderable_value =
402
409
  if block
403
- renderable_function.call(*args) do |*rargs|
410
+ renderable_function.call(*args, **kwargs) do |*rargs|
404
411
  view_context.capture(*rargs, &block)
405
412
  end
406
413
  else
407
- renderable_function.call(*args)
414
+ renderable_function.call(*args, **kwargs)
408
415
  end
409
416
 
410
417
  # Function calls can return components, so if it's a component handle it specially
@@ -426,9 +433,8 @@ module ViewComponent
426
433
 
427
434
  slot
428
435
  end
429
- ruby2_keywords(:set_slot) if respond_to?(:ruby2_keywords, true)
430
436
 
431
- def set_polymorphic_slot(slot_name, poly_type = nil, *args, &block)
437
+ def __vc_set_polymorphic_slot(slot_name, poly_type = nil, *args, **kwargs, &block)
432
438
  slot_definition = self.class.registered_slots[slot_name]
433
439
 
434
440
  if !slot_definition[:collection] && defined?(@__vc_set_slots) && @__vc_set_slots[slot_name]
@@ -437,8 +443,7 @@ module ViewComponent
437
443
 
438
444
  poly_def = slot_definition[:renderable_hash][poly_type]
439
445
 
440
- set_slot(slot_name, poly_def, *args, &block)
446
+ __vc_set_slot(slot_name, poly_def, *args, **kwargs, &block)
441
447
  end
442
- ruby2_keywords(:set_polymorphic_slot) if respond_to?(:ruby2_keywords, true)
443
448
  end
444
449
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module SystemSpecHelpers
5
+ include SystemTestHelpers
6
+
7
+ def page
8
+ Capybara.current_session
9
+ end
10
+ end
11
+ end
@@ -4,7 +4,6 @@ module ViewComponent
4
4
  module SystemTestHelpers
5
5
  include TestHelpers
6
6
 
7
- #
8
7
  # Returns a block that can be used to visit the path of the inline rendered component.
9
8
  # @param fragment [Nokogiri::Fragment] The fragment returned from `render_inline`.
10
9
  # @param layout [String] The (optional) layout to use.
@@ -18,7 +17,7 @@ module ViewComponent
18
17
  file.write(vc_test_controller.render_to_string(html: fragment.to_html.html_safe, layout: layout))
19
18
  file.rewind
20
19
 
21
- block.call("/_system_test_entrypoint?file=#{file.path.split("/").last}")
20
+ yield("/_system_test_entrypoint?file=#{file.path.split("/").last}")
22
21
  ensure
23
22
  file.unlink
24
23
  end