view_component 2.82.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

@@ -31,50 +31,58 @@ module ViewComponent
31
31
  return if component_class == ViewComponent::Base
32
32
 
33
33
  component_class.superclass.compile(raise_errors: raise_errors) if should_compile_superclass?
34
- subclass_instance_methods = component_class.instance_methods(false)
35
-
36
- if subclass_instance_methods.include?(:with_content) && raise_errors
37
- raise ViewComponent::ComponentError.new(
38
- "#{component_class} implements a reserved method, `#with_content`.\n\n" \
39
- "To fix this issue, change the name of the method."
40
- )
41
- end
42
34
 
43
35
  if template_errors.present?
44
- raise ViewComponent::TemplateError.new(template_errors) if raise_errors
36
+ raise TemplateError.new(template_errors) if raise_errors
45
37
 
46
38
  return false
47
39
  end
48
40
 
49
- if subclass_instance_methods.include?(:before_render_check)
50
- ViewComponent::Deprecation.deprecation_warning(
51
- "`before_render_check`", :"`before_render`"
52
- )
53
- end
54
-
55
41
  if raise_errors
56
42
  component_class.validate_initialization_parameters!
57
43
  component_class.validate_collection_parameter!
58
44
  end
59
45
 
60
- templates.each do |template|
61
- # Remove existing compiled template methods,
62
- # as Ruby warns when redefining a method.
63
- method_name = call_method_name(template[:variant])
46
+ if has_inline_template?
47
+ template = component_class.inline_template
64
48
 
65
49
  redefinition_lock.synchronize do
66
- component_class.silence_redefinition_of_method(method_name)
50
+ component_class.silence_redefinition_of_method("call")
67
51
  # rubocop:disable Style/EvalWithLocation
68
- component_class.class_eval <<-RUBY, template[:path], 0
69
- def #{method_name}
70
- #{compiled_template(template[:path])}
52
+ component_class.class_eval <<-RUBY, template.path, template.lineno
53
+ def call
54
+ #{compiled_inline_template(template)}
71
55
  end
72
56
  RUBY
73
57
  # rubocop:enable Style/EvalWithLocation
58
+
59
+ component_class.silence_redefinition_of_method("render_template_for")
60
+ component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
61
+ def render_template_for(variant = nil)
62
+ call
63
+ end
64
+ RUBY
65
+ end
66
+ else
67
+ templates.each do |template|
68
+ # Remove existing compiled template methods,
69
+ # as Ruby warns when redefining a method.
70
+ method_name = call_method_name(template[:variant])
71
+
72
+ redefinition_lock.synchronize do
73
+ component_class.silence_redefinition_of_method(method_name)
74
+ # rubocop:disable Style/EvalWithLocation
75
+ component_class.class_eval <<-RUBY, template[:path], 0
76
+ def #{method_name}
77
+ #{compiled_template(template[:path])}
78
+ end
79
+ RUBY
80
+ # rubocop:enable Style/EvalWithLocation
81
+ end
74
82
  end
75
- end
76
83
 
77
- define_render_template_for
84
+ define_render_template_for
85
+ end
78
86
 
79
87
  component_class.build_i18n_backend
80
88
 
@@ -109,12 +117,16 @@ module ViewComponent
109
117
  end
110
118
  end
111
119
 
120
+ def has_inline_template?
121
+ component_class.respond_to?(:inline_template) && component_class.inline_template.present?
122
+ end
123
+
112
124
  def template_errors
113
125
  @__vc_template_errors ||=
114
126
  begin
115
127
  errors = []
116
128
 
117
- if (templates + inline_calls).empty?
129
+ if (templates + inline_calls).empty? && !has_inline_template?
118
130
  errors << "Couldn't find a template file or inline render method for #{component_class}."
119
131
  end
120
132
 
@@ -222,9 +234,21 @@ module ViewComponent
222
234
  end
223
235
  end
224
236
 
237
+ def compiled_inline_template(template)
238
+ handler = ActionView::Template.handler_for_extension(template.language)
239
+ template.rstrip! if component_class.strip_trailing_whitespace?
240
+
241
+ compile_template(template.source, handler)
242
+ end
243
+
225
244
  def compiled_template(file_path)
226
245
  handler = ActionView::Template.handler_for_extension(File.extname(file_path).delete("."))
227
246
  template = File.read(file_path)
247
+
248
+ compile_template(template, handler)
249
+ end
250
+
251
+ def compile_template(template, handler)
228
252
  template.rstrip! if component_class.strip_trailing_whitespace?
229
253
 
230
254
  if handler.method(:call).parameters.length > 1
@@ -253,8 +277,7 @@ module ViewComponent
253
277
  end
254
278
 
255
279
  def should_compile_superclass?
256
- development? &&
257
- templates.empty? &&
280
+ development? && templates.empty? && !has_inline_template? &&
258
281
  !(
259
282
  component_class.instance_methods(false).include?(:call) ||
260
283
  component_class.private_instance_methods(false).include?(:call)
@@ -23,7 +23,8 @@ module ViewComponent
23
23
  show_previews: Rails.env.development? || Rails.env.test?,
24
24
  preview_paths: default_preview_paths,
25
25
  test_controller: "ApplicationController",
26
- default_preview_layout: nil
26
+ default_preview_layout: nil,
27
+ capture_compatibility_patch_enabled: false
27
28
  })
28
29
  end
29
30
 
@@ -126,9 +127,6 @@ module ViewComponent
126
127
  # The locations in which component previews will be looked up.
127
128
  # Defaults to `['test/component/previews']` relative to your Rails root.
128
129
 
129
- # @!attribute preview_path
130
- # @deprecated Use #preview_paths instead. Will be removed in v3.0.0.
131
-
132
130
  # @!attribute test_controller
133
131
  # @return [String]
134
132
  # The controller used for testing components.
@@ -140,6 +138,13 @@ module ViewComponent
140
138
  # A custom default layout used for the previews index page and individual
141
139
  # previews.
142
140
  # Defaults to `nil`. If this is falsy, `"component_preview"` is used.
141
+ #
142
+ # @!attribute capture_compatibility_patch_enabled
143
+ # @return [Boolean]
144
+ # Enables the experimental capture compatibility patch that makes ViewComponent
145
+ # compatible with forms, capture, and other built-ins.
146
+ # previews.
147
+ # Defaults to `false`.
143
148
 
144
149
  def default_preview_paths
145
150
  return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
@@ -158,15 +163,6 @@ module ViewComponent
158
163
  @config = self.class.defaults
159
164
  end
160
165
 
161
- def preview_path
162
- preview_paths
163
- end
164
-
165
- def preview_path=(new_value)
166
- ViewComponent::Deprecation.deprecation_warning("`preview_path`", :"`preview_paths`")
167
- self.preview_paths = Array.wrap(new_value)
168
- end
169
-
170
166
  delegate_missing_to :config
171
167
 
172
168
  private
@@ -3,6 +3,6 @@
3
3
  require "active_support/deprecation"
4
4
 
5
5
  module ViewComponent
6
- DEPRECATION_HORIZON = "3.0.0"
6
+ DEPRECATION_HORIZON = "4.0.0"
7
7
  Deprecation = ActiveSupport::Deprecation.new(DEPRECATION_HORIZON, "ViewComponent")
8
8
  end
@@ -12,7 +12,11 @@ nav_order: 3
12
12
  ## <%= section.heading %>
13
13
 
14
14
  <% section.methods.each do |method| %>
15
- ### <%== render ViewComponent::DocsBuilderComponent::MethodDoc.new(method) %>
15
+ ### <%== render ViewComponent::DocsBuilderComponent::MethodDoc.new(method, section.show_types) %>
16
+
17
+ <% end %>
18
+ <% section.error_klasses.each do |error_klass| %>
19
+ ### <%== render ViewComponent::DocsBuilderComponent::ErrorKlassDoc.new(error_klass, section.show_types) %>
16
20
 
17
21
  <% end %>
18
22
  <% end %>
@@ -2,21 +2,40 @@
2
2
 
3
3
  module ViewComponent
4
4
  class DocsBuilderComponent < Base
5
- class Section < Struct.new(:heading, :methods, :show_types, keyword_init: true)
6
- def initialize(heading: nil, methods: [], show_types: true)
5
+ class Section < Struct.new(:heading, :methods, :error_klasses, :show_types, keyword_init: true)
6
+ def initialize(heading: nil, methods: [], error_klasses: [], show_types: true)
7
7
  methods.sort_by! { |method| method[:name] }
8
+ error_klasses.sort!
8
9
  super
9
10
  end
10
11
  end
11
12
 
12
- class MethodDoc < ViewComponent::Base
13
- def initialize(method, section: Section.new(show_types: true))
14
- @method = method
15
- @section = section
13
+ class ErrorKlassDoc < ViewComponent::Base
14
+ def initialize(error_klass, _show_types)
15
+ @error_klass = error_klass
16
+ end
17
+
18
+ def klass_name
19
+ @error_klass.gsub("ViewComponent::", "").gsub("::MESSAGE", "")
16
20
  end
17
21
 
18
- def show_types?
19
- @section.show_types
22
+ def error_message
23
+ ViewComponent.const_get(@error_klass)
24
+ end
25
+
26
+ def call
27
+ <<~DOCS.chomp
28
+ `#{klass_name}`
29
+
30
+ #{error_message}
31
+ DOCS
32
+ end
33
+ end
34
+
35
+ class MethodDoc < ViewComponent::Base
36
+ def initialize(method, show_types = true)
37
+ @method = method
38
+ @show_types = show_types
20
39
  end
21
40
 
22
41
  def deprecated?
@@ -28,7 +47,7 @@ module ViewComponent
28
47
  end
29
48
 
30
49
  def types
31
- " → [#{@method.tag(:return).types.join(",")}]" if @method.tag(:return)&.types && show_types?
50
+ " → [#{@method.tag(:return).types.join(",")}]" if @method.tag(:return)&.types && @show_types
32
51
  end
33
52
 
34
53
  def signature_or_name
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails"
4
- require "view_component/base"
4
+ require "view_component/config"
5
5
 
6
6
  module ViewComponent
7
7
  class Engine < Rails::Engine # :nodoc:
8
- config.view_component = ViewComponent::Base.config
8
+ config.view_component = ViewComponent::Config.defaults
9
9
 
10
10
  rake_tasks do
11
11
  load "view_component/rails/tasks/view_component.rake"
@@ -14,9 +14,6 @@ module ViewComponent
14
14
  initializer "view_component.set_configs" do |app|
15
15
  options = app.config.view_component
16
16
 
17
- %i[generate preview_controller preview_route show_previews_source].each do |config_option|
18
- options[config_option] ||= ViewComponent::Base.public_send(config_option)
19
- end
20
17
  options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
21
18
  options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
22
19
  options.show_previews = (Rails.env.development? || Rails.env.test?) if options.show_previews.nil?
@@ -39,6 +36,8 @@ module ViewComponent
39
36
 
40
37
  initializer "view_component.enable_instrumentation" do |app|
41
38
  ActiveSupport.on_load(:view_component) do
39
+ Base.config = app.config.view_component
40
+
42
41
  if app.config.view_component.instrumentation_enabled.present?
43
42
  # :nocov:
44
43
  ViewComponent::Base.prepend(ViewComponent::Instrumentation)
@@ -47,6 +46,14 @@ module ViewComponent
47
46
  end
48
47
  end
49
48
 
49
+ # :nocov:
50
+ initializer "view_component.enable_capture_patch" do |app|
51
+ ActiveSupport.on_load(:view_component) do
52
+ ActionView::Base.include(ViewComponent::CaptureCompatibility) if app.config.view_component.capture_compatibility_patch_enabled
53
+ end
54
+ end
55
+ # :nocov:
56
+
50
57
  initializer "view_component.set_autoload_paths" do |app|
51
58
  options = app.config.view_component
52
59
 
@@ -143,20 +150,3 @@ module ViewComponent
143
150
  end
144
151
  end
145
152
  end
146
-
147
- if RUBY_VERSION < "2.7.0"
148
- ViewComponent::Deprecation.deprecation_warning("Support for Ruby versions < 2.7.0")
149
- end
150
-
151
- # :nocov:
152
- unless defined?(ViewComponent::Base)
153
- require "view_component/deprecation"
154
-
155
- ViewComponent::Deprecation.deprecation_warning(
156
- "Manually loading the engine",
157
- "remove `require \"view_component/engine\"`"
158
- )
159
-
160
- require "view_component"
161
- end
162
- # :nocov:
@@ -0,0 +1,213 @@
1
+ module ViewComponent
2
+ class BaseError < StandardError
3
+ def initialize
4
+ super(self.class::MESSAGE)
5
+ end
6
+ end
7
+
8
+ class DuplicateSlotContentError < StandardError
9
+ MESSAGE =
10
+ "It looks like a block was provided after calling `with_content` on COMPONENT, " \
11
+ "which means that ViewComponent doesn't know which content to use.\n\n" \
12
+ "To fix this issue, use either `with_content` or a block."
13
+
14
+ def initialize(klass_name)
15
+ super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
16
+ end
17
+ end
18
+
19
+ class TemplateError < StandardError
20
+ def initialize(errors)
21
+ super(errors.join(", "))
22
+ end
23
+ end
24
+
25
+ class MultipleInlineTemplatesError < BaseError
26
+ MESSAGE = "Inline templates can only be defined once per-component."
27
+ end
28
+
29
+ class MissingPreviewTemplateError < StandardError
30
+ MESSAGE =
31
+ "A preview template for example EXAMPLE doesn't exist.\n\n" \
32
+ "To fix this issue, create a template for the example."
33
+
34
+ def initialize(example)
35
+ super(MESSAGE.gsub("EXAMPLE", example))
36
+ end
37
+ end
38
+
39
+ class DuplicateContentError < StandardError
40
+ MESSAGE =
41
+ "It looks like a block was provided after calling `with_content` on COMPONENT, " \
42
+ "which means that ViewComponent doesn't know which content to use.\n\n" \
43
+ "To fix this issue, use either `with_content` or a block."
44
+
45
+ def initialize(klass_name)
46
+ super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
47
+ end
48
+ end
49
+
50
+ class EmptyOrInvalidInitializerError < StandardError
51
+ MESSAGE =
52
+ "The COMPONENT initializer is empty or invalid. " \
53
+ "It must accept the parameter `PARAMETER` to render it as a collection.\n\n" \
54
+ "To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
55
+ "See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."
56
+
57
+ def initialize(klass_name, parameter)
58
+ super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
59
+ end
60
+ end
61
+
62
+ class MissingCollectionArgumentError < StandardError
63
+ MESSAGE =
64
+ "The initializer for COMPONENT doesn't accept the parameter `PARAMETER`, " \
65
+ "which is required to render it as a collection.\n\n" \
66
+ "To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
67
+ "See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."
68
+
69
+ def initialize(klass_name, parameter)
70
+ super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
71
+ end
72
+ end
73
+
74
+ class ReservedParameterError < StandardError
75
+ MESSAGE =
76
+ "COMPONENT initializer can't accept the parameter `PARAMETER`, as it will override a " \
77
+ "public ViewComponent method. To fix this issue, rename the parameter."
78
+
79
+ def initialize(klass_name, parameter)
80
+ super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
81
+ end
82
+ end
83
+
84
+ class InvalidCollectionArgumentError < BaseError
85
+ MESSAGE =
86
+ "The value of the first argument passed to `with_collection` isn't a valid collection. " \
87
+ "Make sure it responds to `to_ary`."
88
+ end
89
+
90
+ class ContentSlotNameError < StandardError
91
+ MESSAGE =
92
+ "COMPONENT declares a slot named content, which is a reserved word in ViewComponent.\n\n" \
93
+ "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" \
94
+ "To fix this issue, either use the `content` accessor directly or choose a different slot name."
95
+
96
+ def initialize(klass_name)
97
+ super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
98
+ end
99
+ end
100
+
101
+ class InvalidSlotDefinitionError < BaseError
102
+ MESSAGE =
103
+ "Invalid slot definition. Please pass a class, " \
104
+ "string, or callable (that is proc, lambda, etc)"
105
+ end
106
+
107
+ class SlotPredicateNameError < StandardError
108
+ MESSAGE =
109
+ "COMPONENT declares a slot named SLOT_NAME, which ends with a question mark.\n\n" \
110
+ "This isn't allowed because the ViewComponent framework already provides predicate " \
111
+ "methods ending in `?`.\n\n" \
112
+ "To fix this issue, choose a different name."
113
+
114
+ def initialize(klass_name, slot_name)
115
+ super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
116
+ end
117
+ end
118
+
119
+ class RedefinedSlotError < StandardError
120
+ MESSAGE =
121
+ "COMPONENT declares the SLOT_NAME slot multiple times.\n\n" \
122
+ "To fix this issue, choose a different slot name."
123
+
124
+ def initialize(klass_name, slot_name)
125
+ super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
126
+ end
127
+ end
128
+
129
+ class ReservedSingularSlotNameError < StandardError
130
+ MESSAGE =
131
+ "COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \
132
+ "To fix this issue, choose a different name."
133
+
134
+ def initialize(klass_name, slot_name)
135
+ super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
136
+ end
137
+ end
138
+
139
+ class ReservedPluralSlotNameError < StandardError
140
+ MESSAGE =
141
+ "COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \
142
+ "To fix this issue, choose a different name."
143
+
144
+ def initialize(klass_name, slot_name)
145
+ super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
146
+ end
147
+ end
148
+
149
+ class ContentAlreadySetForPolymorphicSlotError < StandardError
150
+ MESSAGE = "Content for slot SLOT_NAME has already been provided."
151
+
152
+ def initialize(slot_name)
153
+ super(MESSAGE.gsub("SLOT_NAME", slot_name.to_s))
154
+ end
155
+ end
156
+
157
+ class NilWithContentError < BaseError
158
+ MESSAGE =
159
+ "No content provided to `#with_content` for #{self}.\n\n" \
160
+ "To fix this issue, pass a value."
161
+ end
162
+
163
+ class TranslateCalledBeforeRenderError < BaseError
164
+ MESSAGE =
165
+ "`#translate` can't be used during initialization as it depends " \
166
+ "on the view context that only exists once a ViewComponent is passed to " \
167
+ "the Rails render pipeline.\n\n" \
168
+ "It's sometimes possible to fix this issue by moving code dependent on " \
169
+ "`#translate` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
170
+ end
171
+
172
+ class HelpersCalledBeforeRenderError < BaseError
173
+ MESSAGE =
174
+ "`#helpers` can't be used during initialization as it depends " \
175
+ "on the view context that only exists once a ViewComponent is passed to " \
176
+ "the Rails render pipeline.\n\n" \
177
+ "It's sometimes possible to fix this issue by moving code dependent on " \
178
+ "`#helpers` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
179
+ end
180
+
181
+ class ControllerCalledBeforeRenderError < BaseError
182
+ MESSAGE =
183
+ "`#controller` can't be used during initialization, as it depends " \
184
+ "on the view context that only exists once a ViewComponent is passed to " \
185
+ "the Rails render pipeline.\n\n" \
186
+ "It's sometimes possible to fix this issue by moving code dependent on " \
187
+ "`#controller` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
188
+ end
189
+
190
+ class NoMatchingTemplatesForPreviewError < StandardError
191
+ MESSAGE = "Found 0 matches for templates for TEMPLATE_IDENTIFIER."
192
+
193
+ def initialize(template_identifier)
194
+ super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier))
195
+ end
196
+ end
197
+
198
+ class MultipleMatchingTemplatesForPreviewError < StandardError
199
+ MESSAGE = "Found multiple templates for TEMPLATE_IDENTIFIER."
200
+
201
+ def initialize(template_identifier)
202
+ super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier))
203
+ end
204
+ end
205
+
206
+ class SystemTestControllerOnlyAllowedInTestError < BaseError
207
+ MESSAGE = "ViewComponent SystemTest controller must only be called in a test environment for security reasons."
208
+ end
209
+
210
+ class SystemTestControllerNefariousPathError < BaseError
211
+ MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory."
212
+ end
213
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent # :nodoc:
4
+ module InlineTemplate
5
+ extend ActiveSupport::Concern
6
+ Template = Struct.new(:source, :language, :path, :lineno)
7
+
8
+ class_methods do
9
+ def method_missing(method, *args)
10
+ return super if !method.end_with?("_template")
11
+
12
+ if defined?(@__vc_inline_template_defined) && @__vc_inline_template_defined
13
+ raise MultipleInlineTemplatesError
14
+ end
15
+
16
+ if args.size != 1
17
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)"
18
+ end
19
+
20
+ ext = method.to_s.gsub("_template", "")
21
+ template = args.first
22
+
23
+ @__vc_inline_template_language = ext
24
+
25
+ caller = caller_locations(1..1)[0]
26
+ @__vc_inline_template = Template.new(
27
+ template,
28
+ ext,
29
+ caller.absolute_path || caller.path,
30
+ caller.lineno
31
+ )
32
+
33
+ @__vc_inline_template_defined = true
34
+ end
35
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
36
+
37
+ def respond_to_missing?(method, include_all = false)
38
+ method.end_with?("_template") || super
39
+ end
40
+
41
+ def inline_template
42
+ @__vc_inline_template
43
+ end
44
+
45
+ def inline_template_language
46
+ @__vc_inline_template_language if defined?(@__vc_inline_template_language)
47
+ end
48
+
49
+ def inherited(subclass)
50
+ super
51
+ subclass.instance_variable_set(:@__vc_inline_template_language, inline_template_language)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -80,13 +80,7 @@ module ViewComponent # :nodoc:
80
80
  Dir["#{path}/#{preview_name}_preview/#{example}.html.*"].first
81
81
  end
82
82
 
83
- if preview_path.nil?
84
- raise(
85
- PreviewTemplateError,
86
- "A preview template for example #{example} doesn't exist.\n\n" \
87
- "To fix this issue, create a template for the example."
88
- )
89
- end
83
+ raise MissingPreviewTemplateError.new(example) if preview_path.nil?
90
84
 
91
85
  path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
92
86
  Pathname.new(path)
@@ -3,7 +3,7 @@
3
3
  task stats: "view_component:statsetup"
4
4
 
5
5
  namespace :view_component do
6
- task statsetup: :environment do
6
+ task :statsetup do
7
7
  require "rails/code_statistics"
8
8
 
9
9
  ::STATS_DIRECTORIES << ["ViewComponents", ViewComponent::Base.view_component_path]