view_component 3.0.0.rc1 → 3.0.0.rc2

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.

@@ -1,80 +1,396 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/concern"
4
-
5
4
  require "view_component/slot"
6
5
 
7
6
  module ViewComponent
8
7
  module Slotable
9
8
  extend ActiveSupport::Concern
10
9
 
10
+ RESERVED_NAMES = {
11
+ singular: %i[content render].freeze,
12
+ plural: %i[contents renders].freeze
13
+ }.freeze
14
+
15
+ # Setup component slot state
11
16
  included do
12
17
  # Hash of registered Slots
13
- class_attribute :slots
14
- self.slots = {}
18
+ class_attribute :registered_slots
19
+ self.registered_slots = {}
15
20
  end
16
21
 
17
22
  class_methods do
18
- def inherited(child)
19
- # Clone slot configuration into child class
20
- # see #test_slots_pollution
21
- child.slots = slots.clone
23
+ ##
24
+ # Registers a sub-component
25
+ #
26
+ # = Example
27
+ #
28
+ # renders_one :header -> (classes:) do
29
+ # HeaderComponent.new(classes: classes)
30
+ # end
31
+ #
32
+ # # OR
33
+ #
34
+ # renders_one :header, HeaderComponent
35
+ #
36
+ # where `HeaderComponent` is defined as:
37
+ #
38
+ # class HeaderComponent < ViewComponent::Base
39
+ # def initialize(classes:)
40
+ # @classes = classes
41
+ # end
42
+ # end
43
+ #
44
+ # and has the following template:
45
+ #
46
+ # <header class="<%= @classes %>">
47
+ # <%= content %>
48
+ # </header>
49
+ #
50
+ # = Rendering sub-component content
51
+ #
52
+ # The component's sidecar template can access the sub-component by calling a
53
+ # helper method with the same name as the sub-component.
54
+ #
55
+ # <h1>
56
+ # <%= header do %>
57
+ # My header title
58
+ # <% end %>
59
+ # </h1>
60
+ #
61
+ # = Setting sub-component content
62
+ #
63
+ # Consumers of the component can render a sub-component by calling a
64
+ # helper method with the same name as the slot prefixed with `with_`.
65
+ #
66
+ # <%= render_inline(MyComponent.new) do |component| %>
67
+ # <% component.with_header(classes: "Foo") do %>
68
+ # <p>Bar</p>
69
+ # <% end %>
70
+ # <% end %>
71
+ def renders_one(slot_name, callable = nil)
72
+ validate_singular_slot_name(slot_name)
73
+
74
+ if callable.is_a?(Hash) && callable.key?(:types)
75
+ register_polymorphic_slot(slot_name, callable[:types], collection: false)
76
+ else
77
+ validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym)
78
+
79
+ define_method :"with_#{slot_name}" do |*args, &block|
80
+ set_slot(slot_name, nil, *args, &block)
81
+ end
82
+ ruby2_keywords(:"with_#{slot_name}") if respond_to?(:ruby2_keywords, true)
83
+
84
+ define_method slot_name do |*args, &block|
85
+ get_slot(slot_name)
86
+ end
87
+ ruby2_keywords(slot_name.to_sym) if respond_to?(:ruby2_keywords, true)
88
+
89
+ define_method "#{slot_name}?" do
90
+ get_slot(slot_name).present?
91
+ end
92
+
93
+ register_slot(slot_name, collection: false, callable: callable)
94
+ end
95
+ end
96
+
97
+ ##
98
+ # Registers a collection sub-component
99
+ #
100
+ # = Example
101
+ #
102
+ # renders_many :items, -> (name:) { ItemComponent.new(name: name }
103
+ #
104
+ # # OR
105
+ #
106
+ # renders_many :items, ItemComponent
107
+ #
108
+ # = Rendering sub-components
109
+ #
110
+ # The component's sidecar template can access the slot by calling a
111
+ # helper method with the same name as the slot.
112
+ #
113
+ # <h1>
114
+ # <% items.each do |item| %>
115
+ # <%= item %>
116
+ # <% end %>
117
+ # </h1>
118
+ #
119
+ # = Setting sub-component content
120
+ #
121
+ # Consumers of the component can set the content of a slot by calling a
122
+ # helper method with the same name as the slot prefixed with `with_`. The
123
+ # method can be called multiple times to append to the slot.
124
+ #
125
+ # <%= render_inline(MyComponent.new) do |component| %>
126
+ # <% component.with_item(name: "Foo") do %>
127
+ # <p>One</p>
128
+ # <% end %>
129
+ #
130
+ # <% component.with_item(name: "Bar") do %>
131
+ # <p>two</p>
132
+ # <% end %>
133
+ # <% end %>
134
+ def renders_many(slot_name, callable = nil)
135
+ validate_plural_slot_name(slot_name)
136
+
137
+ if callable.is_a?(Hash) && callable.key?(:types)
138
+ register_polymorphic_slot(slot_name, callable[:types], collection: true)
139
+ else
140
+ singular_name = ActiveSupport::Inflector.singularize(slot_name)
141
+ validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym)
142
+
143
+ define_method :"with_#{singular_name}" do |*args, &block|
144
+ set_slot(slot_name, nil, *args, &block)
145
+ end
146
+ ruby2_keywords(:"with_#{singular_name}") if respond_to?(:ruby2_keywords, true)
147
+
148
+ define_method :"with_#{slot_name}" do |collection_args = nil, &block|
149
+ collection_args.map do |args|
150
+ set_slot(slot_name, nil, **args, &block)
151
+ end
152
+ end
22
153
 
154
+ define_method slot_name do |collection_args = nil, &block|
155
+ get_slot(slot_name)
156
+ end
157
+
158
+ define_method "#{slot_name}?" do
159
+ get_slot(slot_name).present?
160
+ end
161
+
162
+ register_slot(slot_name, collection: true, callable: callable)
163
+ end
164
+ end
165
+
166
+ def slot_type(slot_name)
167
+ registered_slot = registered_slots[slot_name]
168
+ if registered_slot
169
+ registered_slot[:collection] ? :collection : :single
170
+ else
171
+ plural_slot_name = ActiveSupport::Inflector.pluralize(slot_name).to_sym
172
+ plural_registered_slot = registered_slots[plural_slot_name]
173
+ plural_registered_slot&.fetch(:collection) ? :collection_item : nil
174
+ end
175
+ end
176
+
177
+ # Clone slot configuration into child class
178
+ # see #test_slots_pollution
179
+ def inherited(child)
180
+ child.registered_slots = registered_slots.clone
23
181
  super
24
182
  end
25
- end
26
183
 
27
- # Build a Slot instance on a component,
28
- # exposing it for use inside the
29
- # component template.
30
- #
31
- # slot: Name of Slot, in symbol form
32
- # **args: Arguments to be passed to Slot initializer
33
- #
34
- # For example:
35
- # <%= render(SlotsComponent.new) do |component| %>
36
- # <% component.slot(:footer, class_names: "footer-class") do %>
37
- # <p>This is my footer!</p>
38
- # <% end %>
39
- # <% end %>
40
- #
41
- def slot(slot_name, **args, &block)
42
- # Raise ArgumentError if `slot` doesn't exist
43
- unless slots.key?(slot_name)
44
- raise ArgumentError.new "Unknown slot '#{slot_name}' - expected one of '#{slots.keys}'"
184
+ def register_polymorphic_slot(slot_name, types, collection:)
185
+ unless types.empty?
186
+ getter_name = slot_name
187
+
188
+ define_method(getter_name) do
189
+ get_slot(slot_name)
190
+ end
191
+
192
+ define_method("#{getter_name}?") do
193
+ get_slot(slot_name).present?
194
+ end
195
+ end
196
+
197
+ renderable_hash = types.each_with_object({}) do |(poly_type, poly_callable), memo|
198
+ memo[poly_type] = define_slot(
199
+ "#{slot_name}_#{poly_type}", collection: collection, callable: poly_callable
200
+ )
201
+
202
+ setter_name =
203
+ if collection
204
+ "#{ActiveSupport::Inflector.singularize(slot_name)}_#{poly_type}"
205
+ else
206
+ "#{slot_name}_#{poly_type}"
207
+ end
208
+
209
+ define_method("with_#{setter_name}") do |*args, &block|
210
+ set_polymorphic_slot(slot_name, poly_type, *args, &block)
211
+ end
212
+ ruby2_keywords(:"with_#{setter_name}") if respond_to?(:ruby2_keywords, true)
213
+ end
214
+
215
+ registered_slots[slot_name] = {
216
+ collection: collection,
217
+ renderable_hash: renderable_hash
218
+ }
45
219
  end
46
220
 
47
- slot = slots[slot_name]
221
+ private
222
+
223
+ def register_slot(slot_name, **kwargs)
224
+ registered_slots[slot_name] = define_slot(slot_name, **kwargs)
225
+ end
48
226
 
49
- # The class name of the Slot, such as Header
50
- slot_class = self.class.const_get(slot[:class_name])
227
+ def define_slot(slot_name, collection:, callable:)
228
+ # Setup basic slot data
229
+ slot = {
230
+ collection: collection
231
+ }
232
+ return slot unless callable
51
233
 
52
- unless slot_class <= ViewComponent::Slot
53
- raise ArgumentError.new "#{slot[:class_name]} must inherit from ViewComponent::Slot"
234
+ # If callable responds to `render_in`, we set it on the slot as a renderable
235
+ if callable.respond_to?(:method_defined?) && callable.method_defined?(:render_in)
236
+ slot[:renderable] = callable
237
+ elsif callable.is_a?(String)
238
+ # If callable is a string, we assume it's referencing an internal class
239
+ slot[:renderable_class_name] = callable
240
+ elsif callable.respond_to?(:call)
241
+ # If slot doesn't respond to `render_in`, we assume it's a proc,
242
+ # define a method, and save a reference to it to call when setting
243
+ method_name = :"_call_#{slot_name}"
244
+ define_method method_name, &callable
245
+ slot[:renderable_function] = instance_method(method_name)
246
+ else
247
+ raise(
248
+ ArgumentError,
249
+ "invalid slot definition. Please pass a class, string, or callable (i.e. proc, lambda, etc)"
250
+ )
251
+ end
252
+
253
+ slot
254
+ end
255
+
256
+ def validate_plural_slot_name(slot_name)
257
+ if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
258
+ raise ArgumentError.new(
259
+ "#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
260
+ "To fix this issue, choose a different name."
261
+ )
262
+ end
263
+
264
+ raise_if_slot_ends_with_question_mark(slot_name)
265
+ raise_if_slot_registered(slot_name)
266
+ end
267
+
268
+ def validate_singular_slot_name(slot_name)
269
+ if slot_name.to_sym == :content
270
+ raise ArgumentError.new(
271
+ "#{self} declares a slot named content, which is a reserved word in ViewComponent.\n\n" \
272
+ "Content passed to a ViewComponent as a block is captured and assigned to the `content` accessor without having to create an explicit slot.\n\n" \
273
+ "To fix this issue, either use the `content` accessor directly or choose a different slot name."
274
+ )
275
+ end
276
+
277
+ if RESERVED_NAMES[:singular].include?(slot_name.to_sym)
278
+ raise ArgumentError.new(
279
+ "#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
280
+ "To fix this issue, choose a different name."
281
+ )
282
+ end
283
+
284
+ raise_if_slot_ends_with_question_mark(slot_name)
285
+ raise_if_slot_registered(slot_name)
286
+ end
287
+
288
+ def raise_if_slot_registered(slot_name)
289
+ if registered_slots.key?(slot_name)
290
+ # TODO remove? This breaks overriding slots when slots are inherited
291
+ raise ArgumentError.new(
292
+ "#{self} declares the #{slot_name} slot multiple times.\n\n" \
293
+ "To fix this issue, choose a different slot name."
294
+ )
295
+ end
296
+ end
297
+
298
+ def raise_if_slot_ends_with_question_mark(slot_name)
299
+ if slot_name.to_s.ends_with?("?")
300
+ raise ArgumentError.new(
301
+ "#{self} declares a slot named #{slot_name}, which ends with a question mark.\n\n" \
302
+ "This is not allowed because the ViewComponent framework already provides predicate " \
303
+ "methods ending in `?`.\n\n" \
304
+ "To fix this issue, choose a different name."
305
+ )
306
+ end
54
307
  end
308
+ end
309
+
310
+ def get_slot(slot_name)
311
+ content unless content_evaluated? # ensure content is loaded so slots will be defined
55
312
 
56
- # Instantiate Slot class, accommodating Slots that don't accept arguments
57
- slot_instance = args.present? ? slot_class.new(**args) : slot_class.new
313
+ slot = self.class.registered_slots[slot_name]
314
+ @__vc_set_slots ||= {}
58
315
 
59
- # Capture block and assign to slot_instance#content
60
- slot_instance.content = view_context.capture(&block).to_s.strip.html_safe if block
316
+ if @__vc_set_slots[slot_name]
317
+ return @__vc_set_slots[slot_name]
318
+ end
61
319
 
62
320
  if slot[:collection]
63
- # Initialize instance variable as an empty array
64
- # if slot is a collection and has yet to be initialized
65
- unless instance_variable_defined?(slot[:instance_variable_name])
66
- instance_variable_set(slot[:instance_variable_name], [])
321
+ []
322
+ end
323
+ end
324
+
325
+ def set_slot(slot_name, slot_definition = nil, *args, &block)
326
+ slot_definition ||= self.class.registered_slots[slot_name]
327
+ slot = Slot.new(self)
328
+
329
+ # Passing the block to the sub-component wrapper like this has two
330
+ # benefits:
331
+ #
332
+ # 1. If this is a `content_area` style sub-component, we will render the
333
+ # block via the `slot`
334
+ #
335
+ # 2. Since we've to pass block content to components when calling
336
+ # `render`, evaluating the block here would require us to call
337
+ # `view_context.capture` twice, which is slower
338
+ slot.__vc_content_block = block if block
339
+
340
+ # If class
341
+ if slot_definition[:renderable]
342
+ slot.__vc_component_instance = slot_definition[:renderable].new(*args)
343
+ # If class name as a string
344
+ elsif slot_definition[:renderable_class_name]
345
+ slot.__vc_component_instance =
346
+ self.class.const_get(slot_definition[:renderable_class_name]).new(*args)
347
+ # If passed a lambda
348
+ elsif slot_definition[:renderable_function]
349
+ # Use `bind(self)` to ensure lambda is executed in the context of the
350
+ # current component. This is necessary to allow the lambda to access helper
351
+ # methods like `content_tag` as well as parent component state.
352
+ renderable_function = slot_definition[:renderable_function].bind(self)
353
+ renderable_value =
354
+ if block
355
+ renderable_function.call(*args) do |*rargs|
356
+ view_context.capture(*rargs, &block)
357
+ end
358
+ else
359
+ renderable_function.call(*args)
360
+ end
361
+
362
+ # Function calls can return components, so if it's a component handle it specially
363
+ if renderable_value.respond_to?(:render_in)
364
+ slot.__vc_component_instance = renderable_value
365
+ else
366
+ slot.__vc_content = renderable_value
67
367
  end
368
+ end
369
+
370
+ @__vc_set_slots ||= {}
68
371
 
69
- # Append Slot instance to collection accessor Array
70
- instance_variable_get(slot[:instance_variable_name]) << slot_instance
372
+ if slot_definition[:collection]
373
+ @__vc_set_slots[slot_name] ||= []
374
+ @__vc_set_slots[slot_name].push(slot)
71
375
  else
72
- # Assign the Slot instance to the slot accessor
73
- instance_variable_set(slot[:instance_variable_name], slot_instance)
376
+ @__vc_set_slots[slot_name] = slot
377
+ end
378
+
379
+ slot
380
+ end
381
+ ruby2_keywords(:set_slot) if respond_to?(:ruby2_keywords, true)
382
+
383
+ def set_polymorphic_slot(slot_name, poly_type = nil, *args, &block)
384
+ slot_definition = self.class.registered_slots[slot_name]
385
+
386
+ if !slot_definition[:collection] && (defined?(@__vc_set_slots) && @__vc_set_slots[slot_name])
387
+ raise ArgumentError, "content for slot '#{slot_name}' has already been provided"
74
388
  end
75
389
 
76
- # Return nil, as this method shouldn't output anything to the view itself.
77
- nil
390
+ poly_def = slot_definition[:renderable_hash][poly_type]
391
+
392
+ set_slot(slot_name, poly_def, *args, &block)
78
393
  end
394
+ ruby2_keywords(:set_polymorphic_slot) if respond_to?(:ruby2_keywords, true)
79
395
  end
80
396
  end
@@ -15,7 +15,7 @@ module ViewComponent
15
15
 
16
16
  file = Tempfile.new(["rendered_#{fragment.class.name}", ".html"], "tmp/view_components/")
17
17
  begin
18
- file.write(controller.render_to_string(html: fragment.to_html.html_safe, layout: layout))
18
+ file.write(__vc_test_helpers_controller.render_to_string(html: fragment.to_html.html_safe, layout: layout))
19
19
  file.rewind
20
20
 
21
21
  block.call("/_system_test_entrypoint?file=#{file.path.split("/").last}")
@@ -28,7 +28,9 @@ module ViewComponent
28
28
  # :nocov:
29
29
  end
30
30
 
31
- # @private
31
+ # Returns the result of a render_inline call.
32
+ #
33
+ # @return [ActionView::OutputBuffer]
32
34
  attr_reader :rendered_content
33
35
 
34
36
  # Render a component inline. Internally sets `page` to be a `Capybara::Node::Simple`,
@@ -45,9 +47,9 @@ module ViewComponent
45
47
  @page = nil
46
48
  @rendered_content =
47
49
  if Rails.version.to_f >= 6.1
48
- controller.view_context.render(component, args, &block)
50
+ __vc_test_helpers_controller.view_context.render(component, args, &block)
49
51
  else
50
- controller.view_context.render_component(component, &block)
52
+ __vc_test_helpers_controller.view_context.render_component(component, &block)
51
53
  end
52
54
 
53
55
  Nokogiri::HTML.fragment(@rendered_content)
@@ -72,8 +74,8 @@ module ViewComponent
72
74
  # @param from [ViewComponent::Preview] The class of the preview to be rendered.
73
75
  # @param params [Hash] Parameters to be passed to the preview.
74
76
  # @return [Nokogiri::HTML]
75
- def render_preview(name, from: preview_class, params: {})
76
- previews_controller = build_controller(Rails.application.config.view_component.preview_controller.constantize)
77
+ def render_preview(name, from: __vc_test_helpers_preview_class, params: {})
78
+ previews_controller = __vc_test_helpers_build_controller(Rails.application.config.view_component.preview_controller.constantize)
77
79
 
78
80
  # From what I can tell, it's not possible to overwrite all request parameters
79
81
  # at once, so we set them individually here.
@@ -103,26 +105,11 @@ module ViewComponent
103
105
  # ```
104
106
  def render_in_view_context(*args, &block)
105
107
  @page = nil
106
- @rendered_content = controller.view_context.instance_exec(*args, &block)
108
+ @rendered_content = __vc_test_helpers_controller.view_context.instance_exec(*args, &block)
107
109
  Nokogiri::HTML.fragment(@rendered_content)
108
110
  end
109
111
  ruby2_keywords(:render_in_view_context) if respond_to?(:ruby2_keywords, true)
110
112
 
111
- # @private
112
- def controller
113
- @controller ||= build_controller(Base.test_controller.constantize)
114
- end
115
-
116
- # @private
117
- def request
118
- @request ||=
119
- begin
120
- request = ActionDispatch::TestRequest.create
121
- request.session = ActionController::TestSession.new
122
- request
123
- end
124
- end
125
-
126
113
  # Set the Action Pack request variant for the given block:
127
114
  #
128
115
  # ```ruby
@@ -133,12 +120,12 @@ module ViewComponent
133
120
  #
134
121
  # @param variant [Symbol] The variant to be set for the provided block.
135
122
  def with_variant(variant)
136
- old_variants = controller.view_context.lookup_context.variants
123
+ old_variants = __vc_test_helpers_controller.view_context.lookup_context.variants
137
124
 
138
- controller.view_context.lookup_context.variants = variant
125
+ __vc_test_helpers_controller.view_context.lookup_context.variants = variant
139
126
  yield
140
127
  ensure
141
- controller.view_context.lookup_context.variants = old_variants
128
+ __vc_test_helpers_controller.view_context.lookup_context.variants = old_variants
142
129
  end
143
130
 
144
131
  # Set the controller to be used while executing the given block,
@@ -152,12 +139,12 @@ module ViewComponent
152
139
  #
153
140
  # @param klass [ActionController::Base] The controller to be used.
154
141
  def with_controller_class(klass)
155
- old_controller = defined?(@controller) && @controller
142
+ old_controller = defined?(@__vc_test_helpers_controller) && @__vc_test_helpers_controller
156
143
 
157
- @controller = build_controller(klass)
144
+ @__vc_test_helpers_controller = __vc_test_helpers_build_controller(klass)
158
145
  yield
159
146
  ensure
160
- @controller = old_controller
147
+ @__vc_test_helpers_controller = old_controller
161
148
  end
162
149
 
163
150
  # Set the URL of the current request (such as when using request-dependent path helpers):
@@ -170,34 +157,47 @@ module ViewComponent
170
157
  #
171
158
  # @param path [String] The path to set for the current request.
172
159
  def with_request_url(path)
173
- old_request_path_info = request.path_info
174
- old_request_path_parameters = request.path_parameters
175
- old_request_query_parameters = request.query_parameters
176
- old_request_query_string = request.query_string
177
- old_controller = defined?(@controller) && @controller
160
+ old_request_path_info = __vc_test_helpers_request.path_info
161
+ old_request_path_parameters = __vc_test_helpers_request.path_parameters
162
+ old_request_query_parameters = __vc_test_helpers_request.query_parameters
163
+ old_request_query_string = __vc_test_helpers_request.query_string
164
+ old_controller = defined?(@__vc_test_helpers_controller) && @__vc_test_helpers_controller
178
165
 
179
166
  path, query = path.split("?", 2)
180
- request.path_info = path
181
- request.path_parameters = Rails.application.routes.recognize_path_with_request(request, path, {})
182
- request.set_header("action_dispatch.request.query_parameters", Rack::Utils.parse_nested_query(query))
183
- request.set_header(Rack::QUERY_STRING, query)
167
+ __vc_test_helpers_request.path_info = path
168
+ __vc_test_helpers_request.path_parameters = Rails.application.routes.recognize_path_with_request(__vc_test_helpers_request, path, {})
169
+ __vc_test_helpers_request.set_header("action_dispatch.request.query_parameters", Rack::Utils.parse_nested_query(query))
170
+ __vc_test_helpers_request.set_header(Rack::QUERY_STRING, query)
184
171
  yield
185
172
  ensure
186
- request.path_info = old_request_path_info
187
- request.path_parameters = old_request_path_parameters
188
- request.set_header("action_dispatch.request.query_parameters", old_request_query_parameters)
189
- request.set_header(Rack::QUERY_STRING, old_request_query_string)
190
- @controller = old_controller
173
+ __vc_test_helpers_request.path_info = old_request_path_info
174
+ __vc_test_helpers_request.path_parameters = old_request_path_parameters
175
+ __vc_test_helpers_request.set_header("action_dispatch.request.query_parameters", old_request_query_parameters)
176
+ __vc_test_helpers_request.set_header(Rack::QUERY_STRING, old_request_query_string)
177
+ @__vc_test_helpers_controller = old_controller
191
178
  end
192
179
 
193
- # @private
194
- def build_controller(klass)
195
- klass.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
180
+ # Note: We prefix private methods here to prevent collisions in consumer's tests.
181
+ private
182
+
183
+ def __vc_test_helpers_controller
184
+ @__vc_test_helpers_controller ||= __vc_test_helpers_build_controller(Base.test_controller.constantize)
196
185
  end
197
186
 
198
- private
187
+ def __vc_test_helpers_request
188
+ @__vc_test_helpers_request ||=
189
+ begin
190
+ out = ActionDispatch::TestRequest.create
191
+ out.session = ActionController::TestSession.new
192
+ out
193
+ end
194
+ end
195
+
196
+ def __vc_test_helpers_build_controller(klass)
197
+ klass.new.tap { |c| c.request = __vc_test_helpers_request }.extend(Rails.application.routes.url_helpers)
198
+ end
199
199
 
200
- def preview_class
200
+ def __vc_test_helpers_preview_class
201
201
  result = if respond_to?(:described_class)
202
202
  raise "`render_preview` expected a described_class, but it is nil." if described_class.nil?
203
203
 
@@ -5,7 +5,7 @@ module ViewComponent
5
5
  MAJOR = 3
6
6
  MINOR = 0
7
7
  PATCH = 0
8
- PRE = "rc1"
8
+ PRE = "rc2"
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
11
11
  end
@@ -7,6 +7,7 @@ module ViewComponent
7
7
  extend ActiveSupport::Autoload
8
8
 
9
9
  autoload :Base
10
+ autoload :CaptureCompatibility
10
11
  autoload :Compiler
11
12
  autoload :CompileCache
12
13
  autoload :ComponentError
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: 3.0.0.rc1
4
+ version: 3.0.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - ViewComponent Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-17 00:00:00.000000000 Z
11
+ date: 2023-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -353,6 +353,7 @@ files:
353
353
  - lib/rails/generators/test_unit/templates/component_test.rb.tt
354
354
  - lib/view_component.rb
355
355
  - lib/view_component/base.rb
356
+ - lib/view_component/capture_compatibility.rb
356
357
  - lib/view_component/collection.rb
357
358
  - lib/view_component/compile_cache.rb
358
359
  - lib/view_component/compiler.rb
@@ -363,7 +364,6 @@ files:
363
364
  - lib/view_component/docs_builder_component.rb
364
365
  - lib/view_component/engine.rb
365
366
  - lib/view_component/instrumentation.rb
366
- - lib/view_component/polymorphic_slots.rb
367
367
  - lib/view_component/preview.rb
368
368
  - lib/view_component/preview_template_error.rb
369
369
  - lib/view_component/rails/tasks/view_component.rake
@@ -373,9 +373,8 @@ files:
373
373
  - lib/view_component/render_to_string_monkey_patch.rb
374
374
  - lib/view_component/rendering_component_helper.rb
375
375
  - lib/view_component/rendering_monkey_patch.rb
376
- - lib/view_component/slot_v2.rb
376
+ - lib/view_component/slot.rb
377
377
  - lib/view_component/slotable.rb
378
- - lib/view_component/slotable_v2.rb
379
378
  - lib/view_component/system_test_case.rb
380
379
  - lib/view_component/system_test_helpers.rb
381
380
  - lib/view_component/template_error.rb