view_component 2.9.0 → 2.13.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of view_component might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc3f38e0c12dafc80bf6c2c9b74d09d3a3f019ee4d4a9dfa59d1a638a1228898
4
- data.tar.gz: 48f1df9b4f54a0049b96e37b1ca16de5125ef13163838aa94cba4fb5cb6f8a1f
3
+ metadata.gz: 3950532a26d2d6d1ddcf412383bfd9cde269c1f69c3d6f7f03dd4e51e7d638e2
4
+ data.tar.gz: ee2524ef62b335376287f30709e62f2d39a71e43c0f5ec9853db8142b6c8fb3c
5
5
  SHA512:
6
- metadata.gz: 7efa511f6cc6b76fed913823eb54d3ec709e3d8ca99adbcbc6cf6a667c407be4885701b68fc174fa2af3908d6da708c77c9cffa58de23f3f11307510e7cceb57
7
- data.tar.gz: 238f0fd897c7125652cf80df6c59dcaa5d8bf5a1eb39d994a48a876be49cf0c43d5274eed3b2fbcf8915461ef1334dec9f92835b886e152e7eb64ab320b7049f
6
+ metadata.gz: c4cc6925445d3486e4c9a6ef915254cf3f28d973ad6a2876fca4fe3435b19ac273f18e89432622eaa38c8bba22ab9e9cafa07e417299dab8537848742175d1c8
7
+ data.tar.gz: 66d2a44a74a07e78f694adc4790218ee7a9d729e8cd8bb9546d154669a79d5abaa20e7d0bffa0801dfc276ef539b93d5a13dd2b4d3d3093cd8d806972d2dac81
@@ -1,5 +1,39 @@
1
1
  # master
2
2
 
3
+ # 2.13.0
4
+
5
+ * Add the ability to disable the render monkey patch with `config.view_component.render_monkey_patch_enabled`. In versions of Rails < 6.1, add `render_component` and `render_component_to_string` methods which can be used for rendering components instead of `render`.
6
+
7
+ *Johannes Engl*
8
+
9
+ # 2.12.0
10
+
11
+ * Implement Slots as potential successor to Content Areas.
12
+
13
+ *Jens Ljungblad, Brian Bugh, Jon Palmer, Joel Hawksley*
14
+
15
+ # 2.11.1
16
+
17
+ * Fix kwarg warnings in Ruby 2.7.
18
+
19
+ *Joel Hawksley*
20
+
21
+ # 2.11.0
22
+
23
+ * Ensure Rails configuration is available within components.
24
+
25
+ *Trevor Broaddus*
26
+
27
+ * Fix bug where global Rails helpers are inaccessible from nested components. Before, `helpers` was pointing to parent component.
28
+
29
+ *Franco Sebregondi*
30
+
31
+ # 2.10.0
32
+
33
+ * Raise an `ArgumentError` with a helpful message when Ruby cannot parse a component class.
34
+
35
+ *Max Beizer*
36
+
3
37
  # 2.9.0
4
38
 
5
39
  * Cache components per-request in development, preventing unnecessary recompilation during a single request.
@@ -16,7 +50,7 @@
16
50
 
17
51
  * Add `rendered_component` method to `ViewComponent::TestHelpers` which exposes the raw output of the rendered component.
18
52
 
19
- *Richard Macklin*
53
+ *Richard Macklin*
20
54
 
21
55
  * Support sidecar directories for views and other assets.
22
56
 
data/README.md CHANGED
@@ -160,6 +160,133 @@ Returning:
160
160
  </div>
161
161
  ```
162
162
 
163
+ #### Slots (experimental)
164
+
165
+ _Slots are currently under development as a successor to Content Areas. The Slot APIs should be considered unfinished and subject to breaking changes in non-major releases of ViewComponent._
166
+
167
+ Slots enable multiple blocks of content to be passed to a single ViewComponent.
168
+
169
+ Slots exist in two forms: normal slots and collection slots.
170
+
171
+ Normal slots can be rendered once per component. They expose an accessor with the name of the slot that returns an instance of `ViewComponent::Slot`, etc.
172
+
173
+ Collection slots can be rendered multiple times. They expose an accessor with the pluralized name of the slot (`#rows`), which is an Array of `ViewComponent::Slot` instances.
174
+
175
+ To learn more about the design of the Slots API, see https://github.com/github/view_component/pull/348.
176
+
177
+ ##### Defining Slots
178
+
179
+ Slots are defined by the `with_slot` macro:
180
+
181
+ `with_slot :header`
182
+
183
+ To define a collection slot, add `collection: true`:
184
+
185
+ `with_slot :row, collection: true`
186
+
187
+ To define a slot with a custom class, pass `class_name`:
188
+
189
+ `with_slot :body, class_name: 'BodySlot`
190
+
191
+ Slot classes should be subclasses of `ViewComponent::Slot`.
192
+
193
+ ##### Example ViewComponent with Slots
194
+
195
+ `# box_component.rb`
196
+ ```ruby
197
+ class BoxComponent < ViewComponent::Base
198
+ include ViewComponent::Slotable
199
+
200
+ with_slot :body, :footer
201
+ with_slot :header, class_name: "Header"
202
+ with_slot :row, collection: true, class_name: "Row"
203
+
204
+ class Header < ViewComponent::Slot
205
+ def initialize(class_names: "")
206
+ @class_names = class_names
207
+ end
208
+
209
+ def class_names
210
+ "Box-header #{@class_names}"
211
+ end
212
+ end
213
+
214
+ class Row < ViewComponent::Slot
215
+ def initialize(theme: :gray)
216
+ @theme = theme
217
+ end
218
+
219
+ def theme_class_name
220
+ case @theme
221
+ when :gray
222
+ "Box-row--gray"
223
+ when :hover_gray
224
+ "Box-row--hover-gray"
225
+ when :yellow
226
+ "Box-row--yellow"
227
+ when :blue
228
+ "Box-row--blue"
229
+ when :hover_blue
230
+ "Box-row--hover-blue"
231
+ else
232
+ "Box-row--gray"
233
+ end
234
+ end
235
+ end
236
+ end
237
+ ```
238
+
239
+ `# box_component.html.erb`
240
+ ```erb
241
+ <div class="Box">
242
+ <% if header %>
243
+ <div class="<%= header.class_names %>">
244
+ <%= header.content %>
245
+ </div>
246
+ <% end %>
247
+ <% if body %>
248
+ <div class="Box-body">
249
+ <%= body.content %>
250
+ </div>
251
+ <% end %>
252
+ <% if rows.any? %>
253
+ <ul>
254
+ <% rows.each do |row| %>
255
+ <li class="Box-row <%= row.theme_class_name %>">
256
+ <%= row.content %>
257
+ </li>
258
+ <% end %>
259
+ </ul>
260
+ <% end %>
261
+ <% if footer %>
262
+ <div class="Box-footer">
263
+ <%= footer %>
264
+ </div>
265
+ <% end %>
266
+ </div>
267
+ ```
268
+
269
+ `# index.html.erb`
270
+ ```erb
271
+ <%= render(BoxComponent.new) do |component| %>
272
+ <% component.slot(:header, class_names: "my-class-name") do %>
273
+ This is my header!
274
+ <% end %>
275
+ <% component.slot(:body) do %>
276
+ This is the body.
277
+ <% end %>
278
+ <% component.slot(:row) do %>
279
+ Row one
280
+ <% end %>
281
+ <% component.slot(:row, theme: :yellow) do %>
282
+ Yellow row
283
+ <% end %>
284
+ <% component.slot(:footer) do %>
285
+ This is the footer.
286
+ <% end %>
287
+ <% end %>
288
+ ```
289
+
163
290
  ### Inline Component
164
291
 
165
292
  ViewComponents can render without a template file, by defining a `call` method:
@@ -584,6 +711,18 @@ To use component previews:
584
711
  config.view_component.preview_path = "#{Rails.root}/spec/components/previews"
585
712
  ```
586
713
 
714
+ ### Disabling the render monkey patch (Rails < 6.1)
715
+
716
+ In order to [avoid conflicts](https://github.com/github/view_component/issues/288) between ViewComponent and other gems that also monkey patch the `render` method, it is possible to configure ViewComponent to not include the render monkey patch:
717
+
718
+ `config.view_component.render_monkey_patch_enabled = false # defaults to true`
719
+
720
+ With the monkey patch disabled, use `render_component` (or `render_component_to_string`) instead:
721
+
722
+ ```
723
+ <%= render_component Component.new(message: "bar") %>
724
+ ```
725
+
587
726
  ### Sidecar assets (experimental)
588
727
 
589
728
  It’s possible to include Javascript and CSS alongside components, sometimes called "sidecar" assets or files.
@@ -731,6 +870,8 @@ ViewComponent is far from a novel idea! Popular implementations of view componen
731
870
  ## Resources
732
871
 
733
872
  - [Encapsulating Views, RailsConf 2020](https://youtu.be/YVYRus_2KZM)
873
+ - [Rethinking the View Layer with Components, Ruby Rogues Podcast](https://devchat.tv/ruby-rogues/rr-461-rethinking-the-view-layer-with-components-with-joel-hawksley/)
874
+ - [ViewComponents in Action with Andrew Mason, Ruby on Rails Podcast](https://5by5.tv/rubyonrails/320)
734
875
  - [ViewComponent at GitHub with Joel Hawksley](https://the-ruby-blend.fireside.fm/9)
735
876
  - [Components, HAML vs ERB, and Design Systems](https://the-ruby-blend.fireside.fm/4)
736
877
  - [Choosing the Right Tech Stack with Dave Paola](https://5by5.tv/rubyonrails/307)
@@ -783,6 +924,16 @@ ViewComponent is built by:
783
924
  |@simonrand|@fugufish|@cover|@franks921|@fsateler|
784
925
  |Dublin, Ireland|Salt Lake City, Utah|Barcelona|South Africa|Chile|
785
926
 
927
+ |<img src="https://avatars.githubusercontent.com/maxbeizer?s=256" alt="maxbeizer" width="128" />|<img src="https://avatars.githubusercontent.com/franco?s=256" alt="franco" width="128" />|<img src="https://avatars.githubusercontent.com/tbroad-ramsey?s=256" alt="tbroad-ramsey" width="128" />|<img src="https://avatars.githubusercontent.com/jensljungblad?s=256" alt="jensljungblad" width="128" />|<img src="https://avatars.githubusercontent.com/bbugh?s=256" alt="bbugh" width="128" />|
928
+ |:---:|:---:|:---:|:---:|:---:|
929
+ |@maxbeizer|@franco|@tbroad-ramsey|@jensljungblad|@bbugh|
930
+ |Nashville, TN|Switzerland|Spring Hill, TN|New York, NY|Austin, TX|
931
+
932
+ |<img src="https://avatars.githubusercontent.com/johannesengl?s=256" alt="johannesengl" width="128" />|
933
+ |:---:|
934
+ |@johannesengl|
935
+ |Berlin, Germany|
936
+
786
937
  ## License
787
938
 
788
939
  ViewComponent is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -5,6 +5,7 @@ require "active_support/configurable"
5
5
  require "view_component/collection"
6
6
  require "view_component/compile_cache"
7
7
  require "view_component/previewable"
8
+ require "view_component/slotable"
8
9
 
9
10
  module ViewComponent
10
11
  class Base < ActionView::Base
@@ -12,11 +13,15 @@ module ViewComponent
12
13
  include ViewComponent::Previewable
13
14
 
14
15
  # For CSRF authenticity tokens in forms
15
- delegate :form_authenticity_token, :protect_against_forgery?, to: :helpers
16
+ delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers
16
17
 
17
18
  class_attribute :content_areas
18
19
  self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2
19
20
 
21
+ # Hash of registered Slots
22
+ class_attribute :slots
23
+ self.slots = {}
24
+
20
25
  # Entrypoint for rendering components.
21
26
  #
22
27
  # view_context: ActionView context from calling view
@@ -106,9 +111,9 @@ module ViewComponent
106
111
  @controller ||= view_context.controller
107
112
  end
108
113
 
109
- # Provides a proxy to access helper methods
114
+ # Provides a proxy to access helper methods from the context of the current controller
110
115
  def helpers
111
- @helpers ||= view_context
116
+ @helpers ||= controller.view_context
112
117
  end
113
118
 
114
119
  # Removes the first part of the path and the extension.
@@ -157,12 +162,15 @@ module ViewComponent
157
162
  mattr_accessor :test_controller
158
163
  @@test_controller = "ApplicationController"
159
164
 
165
+ # Configure if render monkey patches should be included or not in Rails <6.1.
166
+ mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
167
+
160
168
  class << self
161
169
  attr_accessor :source_location
162
170
 
163
171
  # Render a component collection.
164
- def with_collection(*args)
165
- Collection.new(self, *args)
172
+ def with_collection(collection, **args)
173
+ Collection.new(self, collection, **args)
166
174
  end
167
175
 
168
176
  # Provide identifier for ActionView template annotations
@@ -181,6 +189,10 @@ module ViewComponent
181
189
  # has been re-defined by the consuming application, likely in ApplicationComponent.
182
190
  child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].absolute_path
183
191
 
192
+ # Clone slot configuration into child class
193
+ # see #test_slots_pollution
194
+ child.slots = self.slots.clone
195
+
184
196
  super
185
197
  end
186
198
 
@@ -307,7 +319,16 @@ module ViewComponent
307
319
  parameter = validate_default ? collection_parameter : provided_collection_parameter
308
320
 
309
321
  return unless parameter
310
- return if instance_method(:initialize).parameters.map(&:last).include?(parameter)
322
+ return if initialize_parameters.map(&:last).include?(parameter)
323
+
324
+ # If Ruby cannot parse the component class, then the initalize
325
+ # parameters will be empty and ViewComponent will not be able to render
326
+ # the component.
327
+ if initialize_parameters.empty?
328
+ raise ArgumentError.new(
329
+ "#{self} initializer is empty or invalid."
330
+ )
331
+ end
311
332
 
312
333
  raise ArgumentError.new(
313
334
  "#{self} initializer must accept " \
@@ -317,6 +338,10 @@ module ViewComponent
317
338
 
318
339
  private
319
340
 
341
+ def initialize_parameters
342
+ instance_method(:initialize).parameters
343
+ end
344
+
320
345
  def provided_collection_parameter
321
346
  @provided_collection_parameter ||= nil
322
347
  end
@@ -9,7 +9,7 @@ module ViewComponent
9
9
  @component.validate_collection_parameter!(validate_default: true)
10
10
 
11
11
  @collection.map do |item|
12
- content = @component.new(component_options(item, iterator)).render_in(view_context, &block)
12
+ content = @component.new(**component_options(item, iterator)).render_in(view_context, &block)
13
13
  iterator.iterate!
14
14
  content
15
15
  end.join.html_safe
@@ -10,6 +10,7 @@ module ViewComponent
10
10
  initializer "view_component.set_configs" do |app|
11
11
  options = app.config.view_component
12
12
 
13
+ options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
13
14
  options.show_previews = Rails.env.development? if options.show_previews.nil?
14
15
  options.preview_route ||= ViewComponent::Base.preview_route
15
16
 
@@ -42,21 +43,35 @@ module ViewComponent
42
43
  end
43
44
  end
44
45
 
45
- initializer "view_component.monkey_patch_render" do
46
+ initializer "view_component.monkey_patch_render" do |app|
47
+ next if Rails.version.to_f >= 6.1 || !app.config.view_component.render_monkey_patch_enabled
48
+
46
49
  ActiveSupport.on_load(:action_view) do
47
- if Rails.version.to_f < 6.1
48
- require "view_component/render_monkey_patch"
49
- ActionView::Base.prepend ViewComponent::RenderMonkeyPatch
50
- end
50
+ require "view_component/render_monkey_patch"
51
+ ActionView::Base.prepend ViewComponent::RenderMonkeyPatch
51
52
  end
52
53
 
53
54
  ActiveSupport.on_load(:action_controller) do
54
- if Rails.version.to_f < 6.1
55
- require "view_component/rendering_monkey_patch"
56
- require "view_component/render_to_string_monkey_patch"
57
- ActionController::Base.prepend ViewComponent::RenderingMonkeyPatch
58
- ActionController::Base.prepend ViewComponent::RenderToStringMonkeyPatch
59
- end
55
+ require "view_component/rendering_monkey_patch"
56
+ require "view_component/render_to_string_monkey_patch"
57
+ ActionController::Base.prepend ViewComponent::RenderingMonkeyPatch
58
+ ActionController::Base.prepend ViewComponent::RenderToStringMonkeyPatch
59
+ end
60
+ end
61
+
62
+ initializer "view_component.include_render_component" do |app|
63
+ next if Rails.version.to_f >= 6.1
64
+
65
+ ActiveSupport.on_load(:action_view) do
66
+ require "view_component/render_component_helper"
67
+ ActionView::Base.include ViewComponent::RenderComponentHelper
68
+ end
69
+
70
+ ActiveSupport.on_load(:action_controller) do
71
+ require "view_component/rendering_component_helper"
72
+ require "view_component/render_component_to_string_helper"
73
+ ActionController::Base.include ViewComponent::RenderingComponentHelper
74
+ ActionController::Base.include ViewComponent::RenderComponentToStringHelper
60
75
  end
61
76
  end
62
77
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module RenderComponentHelper # :nodoc:
5
+ def render_component(component, &block)
6
+ component.render_in(self, &block)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module RenderComponentToStringHelper # :nodoc:
5
+ def render_component_to_string(component)
6
+ component.render_in(self.view_context)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module RenderingComponentHelper # :nodoc:
5
+ def render_component(component)
6
+ self.response_body = component.render_in(self.view_context)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ class Slot
5
+ attr_accessor :content
6
+ end
7
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ require "view_component/slot"
6
+
7
+ module ViewComponent
8
+ module Slotable
9
+ extend ActiveSupport::Concern
10
+
11
+ class_methods do
12
+ # support initializing slots as:
13
+ #
14
+ # with_slot(
15
+ # :header,
16
+ # collection: true|false,
17
+ # class_name: "Header" # class name string, used to instantiate Slot
18
+ # )
19
+ def with_slot(*slot_names, collection: false, class_name: nil)
20
+ slot_names.each do |slot_name|
21
+ # Ensure slot_name is not already declared
22
+ if self.slots.key?(slot_name)
23
+ raise ArgumentError.new("#{slot_name} slot declared multiple times")
24
+ end
25
+
26
+ # Ensure slot name is not :content
27
+ if slot_name == :content
28
+ raise ArgumentError.new ":content is a reserved slot name. Please use another name, such as ':body'"
29
+ end
30
+
31
+ # Set the name of the method used to access the Slot(s)
32
+ accessor_name =
33
+ if collection
34
+ # If Slot is a collection, set the accessor
35
+ # name to the pluralized form of the slot name
36
+ # For example: :tab => :tabs
37
+ ActiveSupport::Inflector.pluralize(slot_name)
38
+ else
39
+ slot_name
40
+ end
41
+
42
+ instance_variable_name = "@#{accessor_name}"
43
+
44
+ # If the slot is a collection, define an accesor that defaults to an empty array
45
+ if collection
46
+ class_eval <<-RUBY
47
+ def #{accessor_name}
48
+ #{instance_variable_name} ||= []
49
+ end
50
+ RUBY
51
+ else
52
+ attr_reader accessor_name
53
+ end
54
+
55
+ # Default class_name to ViewComponent::Slot
56
+ class_name = "ViewComponent::Slot" unless class_name.present?
57
+
58
+ # Register the slot on the component
59
+ self.slots[slot_name] = {
60
+ class_name: class_name,
61
+ instance_variable_name: instance_variable_name,
62
+ collection: collection
63
+ }
64
+ end
65
+ end
66
+ end
67
+
68
+ # Build a Slot instance on a component,
69
+ # exposing it for use inside the
70
+ # component template.
71
+ #
72
+ # slot: Name of Slot, in symbol form
73
+ # **args: Arguments to be passed to Slot initializer
74
+ #
75
+ # For example:
76
+ # <%= render(SlotsComponent.new) do |component| %>
77
+ # <% component.slot(:footer, class_names: "footer-class") do %>
78
+ # <p>This is my footer!</p>
79
+ # <% end %>
80
+ # <% end %>
81
+ #
82
+ def slot(slot_name, **args, &block)
83
+ # Raise ArgumentError if `slot` does not exist
84
+ unless slots.keys.include?(slot_name)
85
+ raise ArgumentError.new "Unknown slot '#{slot_name}' - expected one of '#{slots.keys}'"
86
+ end
87
+
88
+ slot = slots[slot_name]
89
+
90
+ # The class name of the Slot, such as Header
91
+ slot_class = self.class.const_get(slot[:class_name])
92
+
93
+ unless slot_class <= ViewComponent::Slot
94
+ raise ArgumentError.new "#{slot[:class_name]} must inherit from ViewComponent::Slot"
95
+ end
96
+
97
+ # Instantiate Slot class, accommodating Slots that don't accept arguments
98
+ slot_instance = args.present? ? slot_class.new(args) : slot_class.new
99
+
100
+ # Capture block and assign to slot_instance#content
101
+ slot_instance.content = view_context.capture(&block) if block_given?
102
+
103
+ if slot[:collection]
104
+ # Initialize instance variable as an empty array
105
+ # if slot is a collection and has yet to be initialized
106
+ unless instance_variable_defined?(slot[:instance_variable_name])
107
+ instance_variable_set(slot[:instance_variable_name], [])
108
+ end
109
+
110
+ # Append Slot instance to collection accessor Array
111
+ instance_variable_get(slot[:instance_variable_name]) << slot_instance
112
+ else
113
+ # Assign the Slot instance to the slot accessor
114
+ instance_variable_set(slot[:instance_variable_name], slot_instance)
115
+ end
116
+
117
+ # Return nil, as this method should not output anything to the view itself.
118
+ nil
119
+ end
120
+ end
121
+ end
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 9
6
+ MINOR = 13
7
7
  PATCH = 0
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
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.9.0
4
+ version: 2.13.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: 2020-06-12 00:00:00.000000000 Z
11
+ date: 2020-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -204,9 +204,14 @@ files:
204
204
  - lib/view_component/engine.rb
205
205
  - lib/view_component/preview.rb
206
206
  - lib/view_component/previewable.rb
207
+ - lib/view_component/render_component_helper.rb
208
+ - lib/view_component/render_component_to_string_helper.rb
207
209
  - lib/view_component/render_monkey_patch.rb
208
210
  - lib/view_component/render_to_string_monkey_patch.rb
211
+ - lib/view_component/rendering_component_helper.rb
209
212
  - lib/view_component/rendering_monkey_patch.rb
213
+ - lib/view_component/slot.rb
214
+ - lib/view_component/slotable.rb
210
215
  - lib/view_component/template_error.rb
211
216
  - lib/view_component/test_case.rb
212
217
  - lib/view_component/test_helpers.rb