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