view_component 2.49.1 → 3.23.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/app/assets/vendor/prism.css +3 -195
  4. data/app/assets/vendor/prism.min.js +11 -11
  5. data/app/controllers/concerns/view_component/preview_actions.rb +108 -0
  6. data/app/controllers/view_components_controller.rb +1 -87
  7. data/app/controllers/view_components_system_test_controller.rb +30 -0
  8. data/app/helpers/preview_helper.rb +30 -12
  9. data/app/views/view_components/_preview_source.html.erb +3 -3
  10. data/app/views/view_components/preview.html.erb +2 -2
  11. data/docs/CHANGELOG.md +1653 -24
  12. data/lib/rails/generators/abstract_generator.rb +16 -10
  13. data/lib/rails/generators/component/component_generator.rb +8 -4
  14. data/lib/rails/generators/component/templates/component.rb.tt +3 -2
  15. data/lib/rails/generators/erb/component_generator.rb +1 -1
  16. data/lib/rails/generators/locale/component_generator.rb +4 -4
  17. data/lib/rails/generators/preview/component_generator.rb +17 -3
  18. data/lib/rails/generators/preview/templates/component_preview.rb.tt +5 -1
  19. data/lib/rails/generators/rspec/component_generator.rb +15 -3
  20. data/lib/rails/generators/rspec/templates/component_spec.rb.tt +3 -1
  21. data/lib/rails/generators/stimulus/component_generator.rb +8 -3
  22. data/lib/rails/generators/stimulus/templates/component_controller.ts.tt +9 -0
  23. data/lib/rails/generators/test_unit/templates/component_test.rb.tt +3 -1
  24. data/lib/view_component/base.rb +352 -196
  25. data/lib/view_component/capture_compatibility.rb +44 -0
  26. data/lib/view_component/collection.rb +28 -9
  27. data/lib/view_component/compiler.rb +162 -193
  28. data/lib/view_component/config.rb +225 -0
  29. data/lib/view_component/configurable.rb +17 -0
  30. data/lib/view_component/deprecation.rb +8 -0
  31. data/lib/view_component/engine.rb +74 -47
  32. data/lib/view_component/errors.rb +240 -0
  33. data/lib/view_component/inline_template.rb +55 -0
  34. data/lib/view_component/instrumentation.rb +10 -2
  35. data/lib/view_component/preview.rb +21 -19
  36. data/lib/view_component/rails/tasks/view_component.rake +11 -2
  37. data/lib/view_component/render_component_helper.rb +1 -0
  38. data/lib/view_component/render_component_to_string_helper.rb +1 -1
  39. data/lib/view_component/render_to_string_monkey_patch.rb +1 -1
  40. data/lib/view_component/rendering_component_helper.rb +1 -1
  41. data/lib/view_component/rendering_monkey_patch.rb +1 -1
  42. data/lib/view_component/slot.rb +119 -1
  43. data/lib/view_component/slotable.rb +393 -96
  44. data/lib/view_component/slotable_default.rb +20 -0
  45. data/lib/view_component/system_test_case.rb +13 -0
  46. data/lib/view_component/system_test_helpers.rb +27 -0
  47. data/lib/view_component/template.rb +134 -0
  48. data/lib/view_component/test_helpers.rb +208 -47
  49. data/lib/view_component/translatable.rb +51 -33
  50. data/lib/view_component/use_helpers.rb +42 -0
  51. data/lib/view_component/version.rb +5 -4
  52. data/lib/view_component/with_content_helper.rb +3 -8
  53. data/lib/view_component.rb +7 -12
  54. metadata +339 -57
  55. data/lib/rails/generators/component/USAGE +0 -13
  56. data/lib/view_component/content_areas.rb +0 -57
  57. data/lib/view_component/polymorphic_slots.rb +0 -73
  58. data/lib/view_component/preview_template_error.rb +0 -6
  59. data/lib/view_component/previewable.rb +0 -62
  60. data/lib/view_component/slot_v2.rb +0 -104
  61. data/lib/view_component/slotable_v2.rb +0 -307
  62. data/lib/view_component/template_error.rb +0 -9
  63. data/lib/yard/mattr_accessor_handler.rb +0 -19
@@ -1,147 +1,444 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/concern"
4
-
4
+ require "active_support/inflector/inflections"
5
5
  require "view_component/slot"
6
6
 
7
7
  module ViewComponent
8
8
  module Slotable
9
9
  extend ActiveSupport::Concern
10
10
 
11
+ RESERVED_NAMES = {
12
+ singular: %i[content render].freeze,
13
+ plural: %i[contents renders].freeze
14
+ }.freeze
15
+
16
+ # Setup component slot state
11
17
  included do
12
18
  # Hash of registered Slots
13
- class_attribute :slots
14
- self.slots = {}
19
+ class_attribute :registered_slots
20
+ self.registered_slots = {}
15
21
  end
16
22
 
17
23
  class_methods do
18
- # support initializing slots as:
19
- #
20
- # with_slot(
21
- # :header,
22
- # collection: true|false,
23
- # class_name: "Header" # class name string, used to instantiate Slot
24
- # )
25
- def with_slot(*slot_names, collection: false, class_name: nil)
26
- ActiveSupport::Deprecation.warn(
27
- "`with_slot` is deprecated and will be removed in ViewComponent v3.0.0.\n" \
28
- "Use the new slots API (https://viewcomponent.org/guide/slots.html) instead."
29
- )
30
-
31
- slot_names.each do |slot_name|
32
- # Ensure slot_name isn't already declared
33
- if self.slots.key?(slot_name)
34
- raise ArgumentError.new("#{slot_name} slot declared multiple times")
24
+ ##
25
+ # Registers a sub-component
26
+ #
27
+ # = Example
28
+ #
29
+ # renders_one :header -> (classes:) do
30
+ # HeaderComponent.new(classes: classes)
31
+ # end
32
+ #
33
+ # # OR
34
+ #
35
+ # renders_one :header, HeaderComponent
36
+ #
37
+ # where `HeaderComponent` is defined as:
38
+ #
39
+ # class HeaderComponent < ViewComponent::Base
40
+ # def initialize(classes:)
41
+ # @classes = classes
42
+ # end
43
+ # end
44
+ #
45
+ # and has the following template:
46
+ #
47
+ # <header class="<%= @classes %>">
48
+ # <%= content %>
49
+ # </header>
50
+ #
51
+ # = Rendering sub-component content
52
+ #
53
+ # The component's sidecar template can access the sub-component by calling a
54
+ # helper method with the same name as the sub-component.
55
+ #
56
+ # <h1>
57
+ # <%= header do %>
58
+ # My header title
59
+ # <% end %>
60
+ # </h1>
61
+ #
62
+ # = Setting sub-component content
63
+ #
64
+ # Consumers of the component can render a sub-component by calling a
65
+ # helper method with the same name as the slot prefixed with `with_`.
66
+ #
67
+ # <%= render_inline(MyComponent.new) do |component| %>
68
+ # <% component.with_header(classes: "Foo") do %>
69
+ # <p>Bar</p>
70
+ # <% end %>
71
+ # <% end %>
72
+ #
73
+ # Additionally, content can be set by calling `with_SLOT_NAME_content`
74
+ # on the component instance.
75
+ #
76
+ # <%= render_inline(MyComponent.new.with_header_content("Foo")) %>
77
+ def renders_one(slot_name, callable = nil)
78
+ validate_singular_slot_name(slot_name)
79
+
80
+ if callable.is_a?(Hash) && callable.key?(:types)
81
+ register_polymorphic_slot(slot_name, callable[:types], collection: false)
82
+ else
83
+ validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym)
84
+
85
+ setter_method_name = :"with_#{slot_name}"
86
+
87
+ define_method setter_method_name do |*args, &block|
88
+ set_slot(slot_name, nil, *args, &block)
35
89
  end
90
+ ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
36
91
 
37
- # Ensure slot name isn't :content
38
- if slot_name == :content
39
- raise ArgumentError.new ":content is a reserved slot name. Please use another name, such as ':body'"
92
+ self::GeneratedSlotMethods.define_method slot_name do
93
+ get_slot(slot_name)
40
94
  end
41
95
 
42
- # Set the name of the method used to access the Slot(s)
43
- accessor_name =
44
- if collection
45
- # If Slot is a collection, set the accessor
46
- # name to the pluralized form of the slot name
47
- # For example: :tab => :tabs
48
- ActiveSupport::Inflector.pluralize(slot_name)
49
- else
50
- slot_name
51
- end
96
+ self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
97
+ get_slot(slot_name).present?
98
+ end
52
99
 
53
- instance_variable_name = "@#{accessor_name}"
100
+ define_method :"with_#{slot_name}_content" do |content|
101
+ send(setter_method_name) { content.to_s }
54
102
 
55
- # If the slot is a collection, define an accesor that defaults to an empty array
56
- if collection
57
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
58
- def #{accessor_name}
59
- content unless content_evaluated? # ensure content is loaded so slots will be defined
60
- #{instance_variable_name} ||= []
61
- end
62
- RUBY
63
- else
64
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
65
- def #{accessor_name}
66
- content unless content_evaluated? # ensure content is loaded so slots will be defined
67
- #{instance_variable_name} if defined?(#{instance_variable_name})
103
+ self
104
+ end
105
+
106
+ register_slot(slot_name, collection: false, callable: callable)
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Registers a collection sub-component
112
+ #
113
+ # = Example
114
+ #
115
+ # renders_many :items, -> (name:) { ItemComponent.new(name: name }
116
+ #
117
+ # # OR
118
+ #
119
+ # renders_many :items, ItemComponent
120
+ #
121
+ # = Rendering sub-components
122
+ #
123
+ # The component's sidecar template can access the slot by calling a
124
+ # helper method with the same name as the slot.
125
+ #
126
+ # <h1>
127
+ # <% items.each do |item| %>
128
+ # <%= item %>
129
+ # <% end %>
130
+ # </h1>
131
+ #
132
+ # = Setting sub-component content
133
+ #
134
+ # Consumers of the component can set the content of a slot by calling a
135
+ # helper method with the same name as the slot prefixed with `with_`. The
136
+ # method can be called multiple times to append to the slot.
137
+ #
138
+ # <%= render_inline(MyComponent.new) do |component| %>
139
+ # <% component.with_item(name: "Foo") do %>
140
+ # <p>One</p>
141
+ # <% end %>
142
+ #
143
+ # <% component.with_item(name: "Bar") do %>
144
+ # <p>two</p>
145
+ # <% end %>
146
+ # <% end %>
147
+ def renders_many(slot_name, callable = nil)
148
+ validate_plural_slot_name(slot_name)
149
+
150
+ if callable.is_a?(Hash) && callable.key?(:types)
151
+ register_polymorphic_slot(slot_name, callable[:types], collection: true)
152
+ else
153
+ singular_name = ActiveSupport::Inflector.singularize(slot_name)
154
+ validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym)
155
+
156
+ setter_method_name = :"with_#{singular_name}"
157
+
158
+ define_method setter_method_name do |*args, &block|
159
+ set_slot(slot_name, nil, *args, &block)
160
+ end
161
+ ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
162
+
163
+ define_method :"with_#{singular_name}_content" do |content|
164
+ send(setter_method_name) { content.to_s }
165
+
166
+ self
167
+ end
168
+
169
+ define_method :"with_#{slot_name}" do |collection_args = nil, &block|
170
+ collection_args.map do |args|
171
+ if args.respond_to?(:to_hash)
172
+ set_slot(slot_name, nil, **args, &block)
173
+ else
174
+ set_slot(slot_name, nil, *args, &block)
68
175
  end
69
- RUBY
176
+ end
177
+ end
178
+
179
+ self::GeneratedSlotMethods.define_method slot_name do
180
+ get_slot(slot_name)
181
+ end
182
+
183
+ self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
184
+ get_slot(slot_name).present?
70
185
  end
71
186
 
72
- # Default class_name to ViewComponent::Slot
73
- class_name = "ViewComponent::Slot" unless class_name.present?
187
+ register_slot(slot_name, collection: true, callable: callable)
188
+ end
189
+ end
74
190
 
75
- # Register the slot on the component
76
- self.slots[slot_name] = {
77
- class_name: class_name,
78
- instance_variable_name: instance_variable_name,
79
- collection: collection
80
- }
191
+ def slot_type(slot_name)
192
+ registered_slot = registered_slots[slot_name]
193
+ if registered_slot
194
+ registered_slot[:collection] ? :collection : :single
195
+ else
196
+ plural_slot_name = ActiveSupport::Inflector.pluralize(slot_name).to_sym
197
+ plural_registered_slot = registered_slots[plural_slot_name]
198
+ plural_registered_slot&.fetch(:collection) ? :collection_item : nil
81
199
  end
82
200
  end
83
201
 
84
202
  def inherited(child)
85
203
  # Clone slot configuration into child class
86
204
  # see #test_slots_pollution
87
- child.slots = self.slots.clone
205
+ child.registered_slots = registered_slots.clone
206
+
207
+ # Add a module for slot methods, allowing them to be overriden by the component class
208
+ # see #test_slot_name_can_be_overriden
209
+ unless child.const_defined?(:GeneratedSlotMethods, false)
210
+ generated_slot_methods = Module.new
211
+ child.const_set(:GeneratedSlotMethods, generated_slot_methods)
212
+ child.include generated_slot_methods
213
+ end
88
214
 
89
215
  super
90
216
  end
91
- end
92
217
 
93
- # Build a Slot instance on a component,
94
- # exposing it for use inside the
95
- # component template.
96
- #
97
- # slot: Name of Slot, in symbol form
98
- # **args: Arguments to be passed to Slot initializer
99
- #
100
- # For example:
101
- # <%= render(SlotsComponent.new) do |component| %>
102
- # <% component.slot(:footer, class_names: "footer-class") do %>
103
- # <p>This is my footer!</p>
104
- # <% end %>
105
- # <% end %>
106
- #
107
- def slot(slot_name, **args, &block)
108
- # Raise ArgumentError if `slot` doesn't exist
109
- unless slots.keys.include?(slot_name)
110
- raise ArgumentError.new "Unknown slot '#{slot_name}' - expected one of '#{slots.keys}'"
218
+ def register_polymorphic_slot(slot_name, types, collection:)
219
+ self::GeneratedSlotMethods.define_method(slot_name) do
220
+ get_slot(slot_name)
221
+ end
222
+
223
+ self::GeneratedSlotMethods.define_method(:"#{slot_name}?") do
224
+ get_slot(slot_name).present?
225
+ end
226
+
227
+ renderable_hash = types.each_with_object({}) do |(poly_type, poly_attributes_or_callable), memo|
228
+ if poly_attributes_or_callable.is_a?(Hash)
229
+ poly_callable = poly_attributes_or_callable[:renders]
230
+ poly_slot_name = poly_attributes_or_callable[:as]
231
+ else
232
+ poly_callable = poly_attributes_or_callable
233
+ poly_slot_name = nil
234
+ end
235
+
236
+ poly_slot_name ||=
237
+ if collection
238
+ "#{ActiveSupport::Inflector.singularize(slot_name)}_#{poly_type}"
239
+ else
240
+ "#{slot_name}_#{poly_type}"
241
+ end
242
+
243
+ memo[poly_type] = define_slot(
244
+ poly_slot_name, collection: collection, callable: poly_callable
245
+ )
246
+
247
+ setter_method_name = :"with_#{poly_slot_name}"
248
+
249
+ if instance_methods.include?(setter_method_name)
250
+ raise AlreadyDefinedPolymorphicSlotSetterError.new(setter_method_name, poly_slot_name)
251
+ end
252
+
253
+ define_method(setter_method_name) do |*args, &block|
254
+ set_polymorphic_slot(slot_name, poly_type, *args, &block)
255
+ end
256
+ ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
257
+
258
+ define_method :"with_#{poly_slot_name}_content" do |content|
259
+ send(setter_method_name) { content.to_s }
260
+
261
+ self
262
+ end
263
+ end
264
+
265
+ registered_slots[slot_name] = {
266
+ collection: collection,
267
+ renderable_hash: renderable_hash
268
+ }
269
+ end
270
+
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:)
287
+ slot = {collection: collection}
288
+ return slot unless callable
289
+
290
+ # If callable responds to `render_in`, we set it on the slot as a renderable
291
+ if callable.respond_to?(:method_defined?) && callable.method_defined?(:render_in)
292
+ slot[:renderable] = callable
293
+ elsif callable.is_a?(String)
294
+ # If callable is a string, we assume it's referencing an internal class
295
+ slot[:renderable_class_name] = callable
296
+ elsif callable.respond_to?(:call)
297
+ # If slot doesn't respond to `render_in`, we assume it's a proc,
298
+ # define a method, and save a reference to it to call when setting
299
+ method_name = :"_call_#{slot_name}"
300
+ define_method method_name, &callable
301
+ slot[:renderable_function] = instance_method(method_name)
302
+ else
303
+ raise(InvalidSlotDefinitionError)
304
+ end
305
+
306
+ slot
307
+ end
308
+
309
+ def validate_plural_slot_name(slot_name)
310
+ if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
311
+ raise ReservedPluralSlotNameError.new(name, slot_name)
312
+ end
313
+
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)
318
+ end
319
+
320
+ def validate_singular_slot_name(slot_name)
321
+ if slot_name.to_sym == :content
322
+ raise ContentSlotNameError.new(name)
323
+ end
324
+
325
+ if RESERVED_NAMES[:singular].include?(slot_name.to_sym)
326
+ raise ReservedSingularSlotNameError.new(name, slot_name)
327
+ end
328
+
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)
111
332
  end
112
333
 
113
- slot = slots[slot_name]
334
+ def raise_if_slot_registered(slot_name)
335
+ if registered_slots.key?(slot_name)
336
+ raise RedefinedSlotError.new(name, slot_name)
337
+ end
338
+ end
114
339
 
115
- # The class name of the Slot, such as Header
116
- slot_class = self.class.const_get(slot[:class_name])
340
+ def raise_if_slot_ends_with_question_mark(slot_name)
341
+ raise SlotPredicateNameError.new(name, slot_name) if slot_name.to_s.end_with?("?")
342
+ end
117
343
 
118
- unless slot_class <= ViewComponent::Slot
119
- raise ArgumentError.new "#{slot[:class_name]} must inherit from ViewComponent::Slot"
344
+ def raise_if_slot_conflicts_with_call(slot_name)
345
+ if slot_name.start_with?("call_")
346
+ raise InvalidSlotNameError, "Slot cannot start with 'call_'. Please rename #{slot_name}"
347
+ end
120
348
  end
121
349
 
122
- # Instantiate Slot class, accommodating Slots that don't accept arguments
123
- slot_instance = args.present? ? slot_class.new(**args) : slot_class.new
350
+ def raise_if_slot_name_uncountable(slot_name)
351
+ slot_name = slot_name.to_s
352
+ if slot_name.pluralize == slot_name.singularize
353
+ raise UncountableSlotNameError.new(name, slot_name)
354
+ end
355
+ end
356
+ end
357
+
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]
362
+ @__vc_set_slots ||= {}
124
363
 
125
- # Capture block and assign to slot_instance#content
126
- # rubocop:disable Rails/OutputSafety
127
- slot_instance.content = view_context.capture(&block).to_s.strip.html_safe if block_given?
364
+ if @__vc_set_slots[slot_name]
365
+ return @__vc_set_slots[slot_name]
366
+ end
128
367
 
129
368
  if slot[:collection]
130
- # Initialize instance variable as an empty array
131
- # if slot is a collection and has yet to be initialized
132
- unless instance_variable_defined?(slot[:instance_variable_name])
133
- instance_variable_set(slot[:instance_variable_name], [])
369
+ []
370
+ end
371
+ end
372
+
373
+ def set_slot(slot_name, slot_definition = nil, *args, &block)
374
+ slot_definition ||= self.class.registered_slots[slot_name]
375
+ slot = Slot.new(self)
376
+
377
+ # Passing the block to the sub-component wrapper like this has two
378
+ # benefits:
379
+ #
380
+ # 1. If this is a `content_area` style sub-component, we will render the
381
+ # block via the `slot`
382
+ #
383
+ # 2. Since we have to pass block content to components when calling
384
+ # `render`, evaluating the block here would require us to call
385
+ # `view_context.capture` twice, which is slower
386
+ slot.__vc_content_block = block if block
387
+
388
+ # If class
389
+ if slot_definition[:renderable]
390
+ slot.__vc_component_instance = slot_definition[:renderable].new(*args)
391
+ # If class name as a string
392
+ elsif slot_definition[:renderable_class_name]
393
+ slot.__vc_component_instance =
394
+ self.class.const_get(slot_definition[:renderable_class_name]).new(*args)
395
+ # If passed a lambda
396
+ elsif slot_definition[:renderable_function]
397
+ # Use `bind(self)` to ensure lambda is executed in the context of the
398
+ # current component. This is necessary to allow the lambda to access helper
399
+ # methods like `content_tag` as well as parent component state.
400
+ renderable_function = slot_definition[:renderable_function].bind(self)
401
+ renderable_value =
402
+ if block
403
+ renderable_function.call(*args) do |*rargs|
404
+ view_context.capture(*rargs, &block)
405
+ end
406
+ else
407
+ renderable_function.call(*args)
408
+ end
409
+
410
+ # Function calls can return components, so if it's a component handle it specially
411
+ if renderable_value.respond_to?(:render_in)
412
+ slot.__vc_component_instance = renderable_value
413
+ else
414
+ slot.__vc_content = renderable_value
134
415
  end
416
+ end
417
+
418
+ @__vc_set_slots ||= {}
135
419
 
136
- # Append Slot instance to collection accessor Array
137
- instance_variable_get(slot[:instance_variable_name]) << slot_instance
420
+ if slot_definition[:collection]
421
+ @__vc_set_slots[slot_name] ||= []
422
+ @__vc_set_slots[slot_name].push(slot)
138
423
  else
139
- # Assign the Slot instance to the slot accessor
140
- instance_variable_set(slot[:instance_variable_name], slot_instance)
424
+ @__vc_set_slots[slot_name] = slot
425
+ end
426
+
427
+ slot
428
+ end
429
+ ruby2_keywords(:set_slot) if respond_to?(:ruby2_keywords, true)
430
+
431
+ def set_polymorphic_slot(slot_name, poly_type = nil, *args, &block)
432
+ slot_definition = self.class.registered_slots[slot_name]
433
+
434
+ if !slot_definition[:collection] && defined?(@__vc_set_slots) && @__vc_set_slots[slot_name]
435
+ raise ContentAlreadySetForPolymorphicSlotError.new(slot_name)
141
436
  end
142
437
 
143
- # Return nil, as this method shouldn't output anything to the view itself.
144
- nil
438
+ poly_def = slot_definition[:renderable_hash][poly_type]
439
+
440
+ set_slot(slot_name, poly_def, *args, &block)
145
441
  end
442
+ ruby2_keywords(:set_polymorphic_slot) if respond_to?(:ruby2_keywords, true)
146
443
  end
147
444
  end
@@ -0,0 +1,20 @@
1
+ module ViewComponent
2
+ module SlotableDefault
3
+ def get_slot(slot_name)
4
+ @__vc_set_slots ||= {}
5
+
6
+ return super unless !@__vc_set_slots[slot_name] && (default_method = registered_slots[slot_name][:default_method])
7
+
8
+ renderable_value = send(default_method)
9
+ slot = Slot.new(self)
10
+
11
+ if renderable_value.respond_to?(:render_in)
12
+ slot.__vc_component_instance = renderable_value
13
+ else
14
+ slot.__vc_content = renderable_value
15
+ end
16
+
17
+ slot
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/test_case"
4
+
5
+ module ViewComponent
6
+ class SystemTestCase < ActionDispatch::SystemTestCase
7
+ include ViewComponent::SystemTestHelpers
8
+
9
+ def page
10
+ Capybara.current_session
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module SystemTestHelpers
5
+ include TestHelpers
6
+
7
+ #
8
+ # Returns a block that can be used to visit the path of the inline rendered component.
9
+ # @param fragment [Nokogiri::Fragment] The fragment returned from `render_inline`.
10
+ # @param layout [String] The (optional) layout to use.
11
+ # @return [Proc] A block that can be used to visit the path of the inline rendered component.
12
+ def with_rendered_component_path(fragment, layout: false, &block)
13
+ file = Tempfile.new(
14
+ ["rendered_#{fragment.class.name}", ".html"],
15
+ ViewComponentsSystemTestController.temp_dir
16
+ )
17
+ begin
18
+ file.write(vc_test_controller.render_to_string(html: fragment.to_html.html_safe, layout: layout))
19
+ file.rewind
20
+
21
+ block.call("/_system_test_entrypoint?file=#{file.path.split("/").last}")
22
+ ensure
23
+ file.unlink
24
+ end
25
+ end
26
+ end
27
+ end