view_component 2.19.1 → 2.20.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea6f3a009f297903de456f49af5f615c61c5624fb1deadcb6faa159372d4327d
4
- data.tar.gz: dc5bb14f5e95e34e34aa82c1d004e93891947c1d98f0e70424ab61c2f99c401d
3
+ metadata.gz: '09c638fb23b5832cf8ce411d1a07b50d2d56087858d8186ee2d4248c7e0576de'
4
+ data.tar.gz: b7ea4f439d382228a7c6911b2c0b959be52c7443cd4885a9d91159b55764d8b7
5
5
  SHA512:
6
- metadata.gz: 30afb969cf609a88bacbc8f3eda28bb62c29a3391e6c0b2779de4d5de5a801a28a9289e83d08bae213389949b0c445138d385dbe5df1123f594aca9e60a0ec13
7
- data.tar.gz: be5e974f0efc4c41f60ec092ae14649cfec174f94e79270789610a14081d0ddc3866ab5f7deadf485596a4f44db6e402316d0f9ddffd3b31bd5f9284b9aab810
6
+ metadata.gz: 43d6768b641f4fcb4f7a86488312400f6cb1922722f5654a98bebda771dbdc1a544e84d64b719880c822fec94621f1d402f6eaa805a21d383cab227b630b6503
7
+ data.tar.gz: 152a9f7ad4a6de0fa37df958d8a1877822bdf96f97ef81cbeebd9afb6224155e37725eb71338dd908061c0f0799e0cbf5dcf7c0c39d805ad5c471335649f4b97
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 2.20.0
6
+
7
+ * Don't add `/test/components/previews` to preview_paths if directory doesn't exist.
8
+
9
+ *Andy Holland*
10
+
11
+ * Add `preview_controller` option to override the controller used for component previews.
12
+
13
+ *Matt Swanson, Blake Williams, Juan Manuel Ramallo*
14
+
5
15
  ## 2.19.1
6
16
 
7
17
  * Check if `Rails.application` is loaded.
data/README.md CHANGED
@@ -17,13 +17,7 @@ ViewComponent is tested for compatibility [with combinations of](https://github.
17
17
  In `Gemfile`, add:
18
18
 
19
19
  ```ruby
20
- gem "view_component"
21
- ```
22
-
23
- In `config/application.rb`, add:
24
-
25
- ```bash
26
- require "view_component/engine"
20
+ gem "view_component", require: "view_component/engine"
27
21
  ```
28
22
 
29
23
  ## Guide
@@ -197,7 +191,7 @@ To define a collection slot, add `collection: true`:
197
191
 
198
192
  To define a slot with a custom Ruby class, pass `class_name`:
199
193
 
200
- `with_slot :body, class_name: 'BodySlot`
194
+ `with_slot :body, class_name: 'BodySlot'`
201
195
 
202
196
  _Note: Slot classes must be subclasses of `ViewComponent::Slot`._
203
197
 
@@ -627,7 +621,7 @@ Capybara matchers are available if the gem is installed:
627
621
  require "view_component/test_case"
628
622
 
629
623
  class MyComponentTest < ViewComponent::TestCase
630
- test "render component" do
624
+ def test_render_component
631
625
  render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
632
626
 
633
627
  assert_selector("span[title='my title']", text: "Hello, World!")
@@ -638,7 +632,7 @@ end
638
632
  In the absence of `capybara`, assert against the return value of `render_inline`, which is an instance of `Nokogiri::HTML::DocumentFragment`:
639
633
 
640
634
  ```ruby
641
- test "render component" do
635
+ def test_render_component
642
636
  result = render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
643
637
 
644
638
  assert_includes result.css("span[title='my title']").to_html, "Hello, World!"
@@ -648,7 +642,7 @@ end
648
642
  Alternatively, assert against the raw output of the component, which is exposed as `rendered_component`:
649
643
 
650
644
  ```ruby
651
- test "render component" do
645
+ def test_render_component
652
646
  render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
653
647
 
654
648
  assert_includes rendered_component, "Hello, World!"
@@ -658,7 +652,7 @@ end
658
652
  To test components that use `with_content_areas`:
659
653
 
660
654
  ```ruby
661
- test "renders content_areas template with content " do
655
+ def test_renders_content_areas_template_with_content
662
656
  render_inline(ContentAreasComponent.new(footer: "Bye!")) do |component|
663
657
  component.with(:title, "Hello!")
664
658
  component.with(:body) { "Have a nice day." }
@@ -675,7 +669,7 @@ end
675
669
  Use the `with_variant` helper to test specific variants:
676
670
 
677
671
  ```ruby
678
- test "render component for tablet" do
672
+ def test_render_component_for_tablet
679
673
  with_variant :tablet do
680
674
  render_inline(TestComponent.new(title: "my title")) { "Hello, tablets!" }
681
675
 
@@ -826,9 +820,19 @@ end
826
820
 
827
821
  Which enables passing in a value with <http://localhost:3000/rails/components/cell_component/default?title=Custom+title&subtitle=Another+subtitle>.
828
822
 
823
+ #### Configuring preview controller
824
+
825
+ Previews can be extended to allow users to add authentication, authorization, before actions, or anything that the end user would need to meet their needs using the `preview_controller` option:
826
+
827
+ `config/application.rb`
828
+
829
+ ```ruby
830
+ config.view_component.preview_controller = "MyPreviewController"
831
+ ```
832
+
829
833
  #### Configuring TestController
830
834
 
831
- Component tests and previews assume the existence of an `ApplicationController` class, which be can be configured using the `test_controller` option:
835
+ Component tests assume the existence of an `ApplicationController` class, which be can be configured using the `test_controller` option:
832
836
 
833
837
  `config/application.rb`
834
838
 
@@ -1115,10 +1119,10 @@ ViewComponent is built by:
1115
1119
  |@johannesengl|@czj|@mrrooijen|@bradparker|@mattbrictson|
1116
1120
  |Berlin, Germany|Paris, France|The Netherlands|Brisbane, Australia|San Francisco|
1117
1121
 
1118
- |<img src="https://avatars.githubusercontent.com/mixergtz?s=256" alt="mixergtz" width="128" />|<img src="https://avatars.githubusercontent.com/jules2689?s=256" alt="jules2689" width="128" />|<img src="https://avatars.githubusercontent.com/g13ydson?s=256" alt="g13ydson" width="128" />|
1119
- |:---:|:---:|:---:|
1120
- |@mixergtz|@jules2689|@g13ydson|
1121
- |Medellin, Colombia|Toronto, Canada|João Pessoa, Brazil|
1122
+ |<img src="https://avatars.githubusercontent.com/mixergtz?s=256" alt="mixergtz" width="128" />|<img src="https://avatars.githubusercontent.com/jules2689?s=256" alt="jules2689" width="128" />|<img src="https://avatars.githubusercontent.com/g13ydson?s=256" alt="g13ydson" width="128" />|<img src="https://avatars.githubusercontent.com/swanson?s=256" alt="swanson" width="128" />|
1123
+ |:---:|:---:|:---:|:---:|
1124
+ |@mixergtz|@jules2689|@g13ydson|@swanson|
1125
+ |Medellin, Colombia|Toronto, Canada|João Pessoa, Brazil|Indianapolis, IN|
1122
1126
 
1123
1127
  ## License
1124
1128
 
@@ -1,7 +1,7 @@
1
1
  require "test_helper"
2
2
 
3
3
  class <%= class_name %>ComponentTest < ViewComponent::TestCase
4
- test "component renders something useful" do
4
+ def test_component_renders_something_useful
5
5
  # assert_equal(
6
6
  # %(<span>Hello, components!</span>),
7
7
  # render_inline(<%= class_name %>Component.new(message: "Hello, components!")).css("span").to_html
@@ -6,6 +6,7 @@ module ViewComponent
6
6
  extend ActiveSupport::Autoload
7
7
 
8
8
  autoload :Base
9
+ autoload :Compiler
9
10
  autoload :Preview
10
11
  autoload :PreviewTemplateError
11
12
  autoload :TestHelpers
@@ -77,7 +77,7 @@ module ViewComponent
77
77
  before_render
78
78
 
79
79
  if render?
80
- send(self.class.call_method_name(@variant))
80
+ render_template_for(@variant)
81
81
  else
82
82
  ""
83
83
  end
@@ -208,16 +208,8 @@ module ViewComponent
208
208
  super
209
209
  end
210
210
 
211
- def call_method_name(variant)
212
- if variant.present? && variants.include?(variant)
213
- "call_#{variant}"
214
- else
215
- "call"
216
- end
217
- end
218
-
219
211
  def compiled?
220
- CompileCache.compiled?(self)
212
+ template_compiler.compiled?
221
213
  end
222
214
 
223
215
  # Compile templates to instance methods, assuming they haven't been compiled already.
@@ -225,75 +217,11 @@ module ViewComponent
225
217
  # Do as much work as possible in this step, as doing so reduces the amount
226
218
  # of work done each time a component is rendered.
227
219
  def compile(raise_errors: false)
228
- return if compiled?
229
-
230
- if template_errors.present?
231
- raise ViewComponent::TemplateError.new(template_errors) if raise_errors
232
- return false
233
- end
234
-
235
- if instance_methods(false).include?(:before_render_check)
236
- ActiveSupport::Deprecation.warn(
237
- "`before_render_check` will be removed in v3.0.0. Use `before_render` instead."
238
- )
239
- end
240
-
241
- # Remove any existing singleton methods,
242
- # as Ruby warns when redefining a method.
243
- remove_possible_singleton_method(:variants)
244
- remove_possible_singleton_method(:collection_parameter)
245
- remove_possible_singleton_method(:collection_counter_parameter)
246
- remove_possible_singleton_method(:counter_argument_present?)
247
-
248
- define_singleton_method(:variants) do
249
- templates.map { |template| template[:variant] } + variants_from_inline_calls(inline_calls)
250
- end
251
-
252
- define_singleton_method(:collection_parameter) do
253
- if provided_collection_parameter
254
- provided_collection_parameter
255
- else
256
- name.demodulize.underscore.chomp("_component").to_sym
257
- end
258
- end
259
-
260
- define_singleton_method(:collection_counter_parameter) do
261
- "#{collection_parameter}_counter".to_sym
262
- end
263
-
264
- define_singleton_method(:counter_argument_present?) do
265
- instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
266
- end
267
-
268
- validate_collection_parameter! if raise_errors
269
-
270
- # If template name annotations are turned on, a line is dynamically
271
- # added with a comment. In this case, we want to return a different
272
- # starting line number so errors that are raised will point to the
273
- # correct line in the component template.
274
- line_number =
275
- if ActionView::Base.respond_to?(:annotate_rendered_view_with_filenames) &&
276
- ActionView::Base.annotate_rendered_view_with_filenames
277
- -2
278
- else
279
- -1
280
- end
281
-
282
- templates.each do |template|
283
- # Remove existing compiled template methods,
284
- # as Ruby warns when redefining a method.
285
- method_name = call_method_name(template[:variant])
286
- undef_method(method_name.to_sym) if instance_methods.include?(method_name.to_sym)
287
-
288
- class_eval <<-RUBY, template[:path], line_number
289
- def #{method_name}
290
- @output_buffer = ActionView::OutputBuffer.new
291
- #{compiled_template(template[:path])}
292
- end
293
- RUBY
294
- end
220
+ template_compiler.compile(raise_errors: raise_errors)
221
+ end
295
222
 
296
- CompileCache.register self
223
+ def template_compiler
224
+ @_template_compiler ||= Compiler.new(self)
297
225
  end
298
226
 
299
227
  # we'll eventually want to update this to support other types
@@ -358,111 +286,6 @@ module ViewComponent
358
286
  @provided_collection_parameter ||= nil
359
287
  end
360
288
 
361
- def compiled_template(file_path)
362
- handler = ActionView::Template.handler_for_extension(File.extname(file_path).gsub(".", ""))
363
- template = File.read(file_path)
364
-
365
- if handler.method(:call).parameters.length > 1
366
- handler.call(self, template)
367
- else
368
- handler.call(OpenStruct.new(source: template, identifier: identifier, type: type))
369
- end
370
- end
371
-
372
- def inline_calls
373
- @inline_calls ||=
374
- begin
375
- # Fetch only ViewComponent ancestor classes to limit the scope of
376
- # finding inline calls
377
- view_component_ancestors =
378
- ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } - included_modules
379
-
380
- view_component_ancestors.flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call/) }.uniq
381
- end
382
- end
383
-
384
- def inline_calls_defined_on_self
385
- @inline_calls_defined_on_self ||= instance_methods(false).grep(/^call/)
386
- end
387
-
388
- def matching_views_in_source_location
389
- return [] unless source_location
390
-
391
- location_without_extension = source_location.chomp(File.extname(source_location))
392
-
393
- extensions = ActionView::Template.template_handler_extensions.join(",")
394
-
395
- # view files in the same directory as the component
396
- sidecar_files = Dir["#{location_without_extension}.*{#{extensions}}"]
397
-
398
- # view files in a directory named like the component
399
- directory = File.dirname(source_location)
400
- filename = File.basename(source_location, ".rb")
401
- component_name = name.demodulize.underscore
402
-
403
- sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"]
404
-
405
- (sidecar_files - [source_location] + sidecar_directory_files)
406
- end
407
-
408
- def templates
409
- @templates ||=
410
- matching_views_in_source_location.each_with_object([]) do |path, memo|
411
- pieces = File.basename(path).split(".")
412
-
413
- memo << {
414
- path: path,
415
- variant: pieces.second.split("+").second&.to_sym,
416
- handler: pieces.last
417
- }
418
- end
419
- end
420
-
421
- def template_errors
422
- @template_errors ||=
423
- begin
424
- errors = []
425
-
426
- if (templates + inline_calls).empty?
427
- errors << "Could not find a template file or inline render method for #{self}."
428
- end
429
-
430
- if templates.count { |template| template[:variant].nil? } > 1
431
- errors << "More than one template found for #{self}. There can only be one default template file per component."
432
- end
433
-
434
- invalid_variants = templates
435
- .group_by { |template| template[:variant] }
436
- .map { |variant, grouped| variant if grouped.length > 1 }
437
- .compact
438
- .sort
439
-
440
- unless invalid_variants.empty?
441
- errors << "More than one template found for #{'variant'.pluralize(invalid_variants.count)} #{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{self}. There can only be one template file per variant."
442
- end
443
-
444
- if templates.find { |template| template[:variant].nil? } && inline_calls_defined_on_self.include?(:call)
445
- errors << "Template file and inline render method found for #{self}. There can only be a template file or inline render method per component."
446
- end
447
-
448
- duplicate_template_file_and_inline_variant_calls =
449
- templates.pluck(:variant) & variants_from_inline_calls(inline_calls_defined_on_self)
450
-
451
- unless duplicate_template_file_and_inline_variant_calls.empty?
452
- count = duplicate_template_file_and_inline_variant_calls.count
453
-
454
- errors << "Template #{'file'.pluralize(count)} and inline render #{'method'.pluralize(count)} found for #{'variant'.pluralize(count)} #{duplicate_template_file_and_inline_variant_calls.map { |v| "'#{v}'" }.to_sentence} in #{self}. There can only be a template file or inline render method per variant."
455
- end
456
-
457
- errors
458
- end
459
- end
460
-
461
- def variants_from_inline_calls(calls)
462
- calls.reject { |call| call == :call }.map do |variant_call|
463
- variant_call.to_s.sub("call_", "").to_sym
464
- end
465
- end
466
289
  end
467
290
 
468
291
  ActiveSupport.run_load_hooks(:view_component, self)
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ class Compiler
5
+ def initialize(component_class)
6
+ @component_class = component_class
7
+ end
8
+
9
+ def compiled?
10
+ CompileCache.compiled?(component_class)
11
+ end
12
+
13
+ def compile(raise_errors: false)
14
+ return if compiled?
15
+
16
+ if template_errors.present?
17
+ raise ViewComponent::TemplateError.new(template_errors) if raise_errors
18
+ return false
19
+ end
20
+
21
+ if component_class.instance_methods(false).include?(:before_render_check)
22
+ ActiveSupport::Deprecation.warn(
23
+ "`before_render_check` will be removed in v3.0.0. Use `before_render` instead."
24
+ )
25
+ end
26
+
27
+ # Remove any existing singleton methods,
28
+ # as Ruby warns when redefining a method.
29
+ component_class.remove_possible_singleton_method(:collection_parameter)
30
+ component_class.remove_possible_singleton_method(:collection_counter_parameter)
31
+ component_class.remove_possible_singleton_method(:counter_argument_present?)
32
+
33
+ component_class.define_singleton_method(:collection_parameter) do
34
+ if provided_collection_parameter
35
+ provided_collection_parameter
36
+ else
37
+ name.demodulize.underscore.chomp("_component").to_sym
38
+ end
39
+ end
40
+
41
+ component_class.define_singleton_method(:collection_counter_parameter) do
42
+ "#{collection_parameter}_counter".to_sym
43
+ end
44
+
45
+ component_class.define_singleton_method(:counter_argument_present?) do
46
+ instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
47
+ end
48
+
49
+ component_class.validate_collection_parameter! if raise_errors
50
+
51
+ templates.each do |template|
52
+ # Remove existing compiled template methods,
53
+ # as Ruby warns when redefining a method.
54
+ method_name = call_method_name(template[:variant])
55
+ component_class.send(:undef_method, method_name.to_sym) if component_class.instance_methods.include?(method_name.to_sym)
56
+
57
+ component_class.class_eval <<-RUBY, template[:path], -1
58
+ def #{method_name}
59
+ @output_buffer = ActionView::OutputBuffer.new
60
+ #{compiled_template(template[:path])}
61
+ end
62
+ RUBY
63
+ end
64
+
65
+ define_render_template_for
66
+
67
+ CompileCache.register(component_class)
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :component_class
73
+
74
+ def define_render_template_for
75
+ component_class.send(:undef_method, :render_template_for) if component_class.instance_methods.include?(:render_template_for)
76
+
77
+ variant_elsifs = variants.compact.uniq.map do |variant|
78
+ "elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
79
+ end.join("\n")
80
+
81
+ component_class.class_eval <<-RUBY
82
+ def render_template_for(variant = nil)
83
+ if variant.nil?
84
+ call
85
+ #{variant_elsifs}
86
+ else
87
+ call
88
+ end
89
+ end
90
+ RUBY
91
+
92
+ end
93
+
94
+ def template_errors
95
+ @_template_errors ||= begin
96
+ errors = []
97
+
98
+ if (templates + inline_calls).empty?
99
+ errors << "Could not find a template file or inline render method for #{component_class}."
100
+ end
101
+
102
+ if templates.count { |template| template[:variant].nil? } > 1
103
+ errors << "More than one template found for #{component_class}. There can only be one default template file per component."
104
+ end
105
+
106
+ invalid_variants = templates
107
+ .group_by { |template| template[:variant] }
108
+ .map { |variant, grouped| variant if grouped.length > 1 }
109
+ .compact
110
+ .sort
111
+
112
+ unless invalid_variants.empty?
113
+ errors << "More than one template found for #{'variant'.pluralize(invalid_variants.count)} #{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{component_class}. There can only be one template file per variant."
114
+ end
115
+
116
+ if templates.find { |template| template[:variant].nil? } && inline_calls_defined_on_self.include?(:call)
117
+ errors << "Template file and inline render method found for #{component_class}. There can only be a template file or inline render method per component."
118
+ end
119
+
120
+ duplicate_template_file_and_inline_variant_calls =
121
+ templates.pluck(:variant) & variants_from_inline_calls(inline_calls_defined_on_self)
122
+
123
+ unless duplicate_template_file_and_inline_variant_calls.empty?
124
+ count = duplicate_template_file_and_inline_variant_calls.count
125
+
126
+ errors << "Template #{'file'.pluralize(count)} and inline render #{'method'.pluralize(count)} found for #{'variant'.pluralize(count)} #{duplicate_template_file_and_inline_variant_calls.map { |v| "'#{v}'" }.to_sentence} in #{component_class}. There can only be a template file or inline render method per variant."
127
+ end
128
+
129
+ errors
130
+ end
131
+ end
132
+
133
+ def templates
134
+ @templates ||= matching_views_in_source_location.each_with_object([]) do |path, memo|
135
+ pieces = File.basename(path).split(".")
136
+
137
+ memo << {
138
+ path: path,
139
+ variant: pieces.second.split("+").second&.to_sym,
140
+ handler: pieces.last
141
+ }
142
+ end
143
+ end
144
+
145
+ def matching_views_in_source_location
146
+ source_location = component_class.source_location
147
+ return [] unless source_location
148
+
149
+ location_without_extension = source_location.chomp(File.extname(source_location))
150
+
151
+ extensions = ActionView::Template.template_handler_extensions.join(",")
152
+
153
+ # view files in the same directory as the component
154
+ sidecar_files = Dir["#{location_without_extension}.*{#{extensions}}"]
155
+
156
+ # view files in a directory named like the component
157
+ directory = File.dirname(source_location)
158
+ filename = File.basename(source_location, ".rb")
159
+ component_name = component_class.name.demodulize.underscore
160
+
161
+ sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"]
162
+
163
+ (sidecar_files - [source_location] + sidecar_directory_files)
164
+ end
165
+
166
+ def inline_calls
167
+ @inline_calls ||= begin
168
+ # Fetch only ViewComponent ancestor classes to limit the scope of
169
+ # finding inline calls
170
+ view_component_ancestors =
171
+ component_class.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } - component_class.included_modules
172
+
173
+ view_component_ancestors.flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call/) }.uniq
174
+ end
175
+ end
176
+
177
+ def inline_calls_defined_on_self
178
+ @inline_calls_defined_on_self ||= component_class.instance_methods(false).grep(/^call/)
179
+ end
180
+
181
+ def variants
182
+ @_variants = (
183
+ templates.map { |template| template[:variant] } + variants_from_inline_calls(inline_calls)
184
+ ).compact.uniq
185
+ end
186
+
187
+ def variants_from_inline_calls(calls)
188
+ calls.reject { |call| call == :call }.map do |variant_call|
189
+ variant_call.to_s.sub("call_", "").to_sym
190
+ end
191
+ end
192
+
193
+ # :nocov:
194
+ def compiled_template(file_path)
195
+ handler = ActionView::Template.handler_for_extension(File.extname(file_path).gsub(".", ""))
196
+ template = File.read(file_path)
197
+
198
+ if handler.method(:call).parameters.length > 1
199
+ handler.call(component_class, template)
200
+ else
201
+ handler.call(OpenStruct.new(source: template, identifier: component_class.identifier, type: component_class.type))
202
+ end
203
+ end
204
+ # :nocov:
205
+
206
+ def call_method_name(variant)
207
+ if variant.present? && variants.include?(variant)
208
+ "call_#{variant}"
209
+ else
210
+ "call"
211
+ end
212
+ end
213
+ end
214
+ end
@@ -14,9 +14,12 @@ module ViewComponent
14
14
  options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
15
15
  options.show_previews = Rails.env.development? if options.show_previews.nil?
16
16
  options.preview_route ||= ViewComponent::Base.preview_route
17
+ options.preview_controller ||= ViewComponent::Base.preview_controller
17
18
 
18
19
  if options.show_previews
19
- options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root)
20
+ options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?(
21
+ "#{Rails.root}/test/components/previews"
22
+ )
20
23
 
21
24
  if options.preview_path.present?
22
25
  ActiveSupport::Deprecation.warn(
@@ -87,9 +90,11 @@ module ViewComponent
87
90
  options = app.config.view_component
88
91
 
89
92
  if options.show_previews
90
- app.routes.prepend do
91
- get options.preview_route, to: "view_components#index", as: :preview_view_components, internal: true
92
- get "#{options.preview_route}/*path", to: "view_components#previews", as: :preview_view_component, internal: true
93
+ app.routes.append do
94
+ preview_controller = options.preview_controller.sub(/Controller$/, "").underscore
95
+
96
+ get options.preview_route, to: "#{preview_controller}#index", as: :preview_view_components, internal: true
97
+ get "#{options.preview_route}/*path", to: "#{preview_controller}#previews", as: :preview_view_component, internal: true
93
98
  end
94
99
  end
95
100
 
@@ -80,7 +80,10 @@ module ViewComponent # :nodoc:
80
80
  end
81
81
 
82
82
  path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
83
- Pathname.new(path).relative_path_from(Pathname.new(preview_path)).to_s
83
+ Pathname.new(path)
84
+ .relative_path_from(Pathname.new(preview_path))
85
+ .to_s
86
+ .sub(/\..*$/, "")
84
87
  end
85
88
 
86
89
  private
@@ -41,6 +41,16 @@ module ViewComponent # :nodoc:
41
41
  mattr_accessor :preview_route, instance_writer: false do
42
42
  "/rails/view_components"
43
43
  end
44
+
45
+ # Set the controller to be used for previewing components through app configuration:
46
+ #
47
+ # config.view_component.preview_controller = "MyPreviewController"
48
+ #
49
+ # Defaults to the provided +ViewComponentsController+
50
+ #
51
+ mattr_accessor :preview_controller, instance_writer: false do
52
+ "ViewComponentsController"
53
+ end
44
54
  end
45
55
  end
46
56
  end
@@ -3,8 +3,8 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 19
7
- PATCH = 1
6
+ MINOR = 20
7
+ PATCH = 0
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
10
10
  end
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.19.1
4
+ version: 2.20.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-09-21 00:00:00.000000000 Z
11
+ date: 2020-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -215,6 +215,7 @@ files:
215
215
  - lib/view_component/base.rb
216
216
  - lib/view_component/collection.rb
217
217
  - lib/view_component/compile_cache.rb
218
+ - lib/view_component/compiler.rb
218
219
  - lib/view_component/engine.rb
219
220
  - lib/view_component/preview.rb
220
221
  - lib/view_component/preview_template_error.rb