view_component 2.57.0 → 2.59.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.
- checksums.yaml +4 -4
- data/app/controllers/concerns/view_component/preview_actions.rb +7 -4
- data/app/helpers/preview_helper.rb +3 -3
- data/docs/CHANGELOG.md +103 -0
- data/lib/rails/generators/abstract_generator.rb +4 -4
- data/lib/view_component/base.rb +82 -25
- data/lib/view_component/capybara_simple_session.rb +132 -0
- data/lib/view_component/collection.rb +9 -2
- data/lib/view_component/compiler.rb +23 -9
- data/lib/view_component/content_areas.rb +1 -1
- data/lib/view_component/docs_builder_component.rb +1 -1
- data/lib/view_component/engine.rb +6 -5
- data/lib/view_component/global_output_buffer.rb +4 -3
- data/lib/view_component/output_buffer_stack.rb +0 -2
- data/lib/view_component/polymorphic_slots.rb +17 -2
- data/lib/view_component/preview.rb +8 -6
- data/lib/view_component/render_component_to_string_helper.rb +1 -1
- data/lib/view_component/render_to_string_monkey_patch.rb +1 -1
- data/lib/view_component/rendering_component_helper.rb +1 -1
- data/lib/view_component/rendering_monkey_patch.rb +1 -1
- data/lib/view_component/slotable.rb +5 -6
- data/lib/view_component/slotable_v2.rb +7 -9
- data/lib/view_component/test_helpers.rb +28 -1
- data/lib/view_component/translatable.rb +9 -10
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component.rb +1 -0
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca82494216cd6b91d9a073bd1ab025cbe48049560893a500e72caff01141e02f
|
|
4
|
+
data.tar.gz: 9eecd79663f09edb31cae5a104b861c4922a99a92307f583b0703b11053a2d60
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4e04336d18bd57be41e3ae4637ae1978a4884a352fced4b852a40901fa1ff02a13e6266ab4c669fbb3669fa718eecbfd8a5b13c54df79cbb9b692e278f02f35e
|
|
7
|
+
data.tar.gz: 2fed6153f148f683afe4cd27c909051aedfc92e9f896e6fe41736083d05217e7df15ac12799291fd2299ca59c1ee12e7ec1145ac9be5ee78cacb81d55fccf548
|
|
@@ -37,21 +37,24 @@ module ViewComponent
|
|
|
37
37
|
opts = {}
|
|
38
38
|
opts[:layout] = layout if layout.present? || layout == false
|
|
39
39
|
opts[:locals] = locals if locals.present?
|
|
40
|
-
render "view_components/preview", opts
|
|
40
|
+
render "view_components/preview", opts
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
private
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
# :doc:
|
|
47
|
+
def default_preview_layout
|
|
47
48
|
ViewComponent::Base.default_preview_layout
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
# :doc:
|
|
52
|
+
def show_previews?
|
|
51
53
|
ViewComponent::Base.show_previews
|
|
52
54
|
end
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
# :doc:
|
|
57
|
+
def find_preview
|
|
55
58
|
candidates = []
|
|
56
59
|
params[:path].to_s.scan(%r{/|$}) { candidates << $` }
|
|
57
60
|
preview = candidates.detect { |candidate| ViewComponent::Preview.exists?(candidate) }
|
|
@@ -7,14 +7,14 @@ module PreviewHelper
|
|
|
7
7
|
def preview_source
|
|
8
8
|
return if @render_args.nil?
|
|
9
9
|
|
|
10
|
-
render "preview_source"
|
|
10
|
+
render "preview_source"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def find_template_data(lookup_context:, template_identifier:)
|
|
14
14
|
template = lookup_context.find_template(template_identifier)
|
|
15
15
|
|
|
16
16
|
if Rails.version.to_f >= 6.1 || template.source.present?
|
|
17
|
-
|
|
17
|
+
{
|
|
18
18
|
source: template.source,
|
|
19
19
|
prism_language_name: prism_language_name_by_template(template: template)
|
|
20
20
|
}
|
|
@@ -40,7 +40,7 @@ module PreviewHelper
|
|
|
40
40
|
template_source = File.read(template_file_path)
|
|
41
41
|
prism_language_name = prism_language_name_by_template_path(template_file_path: template_file_path)
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
{
|
|
44
44
|
source: template_source,
|
|
45
45
|
prism_language_name: prism_language_name
|
|
46
46
|
}
|
data/docs/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,109 @@ title: Changelog
|
|
|
9
9
|
|
|
10
10
|
## main
|
|
11
11
|
|
|
12
|
+
## 2.59.0
|
|
13
|
+
|
|
14
|
+
* Expose Capybara DSL methods directly inside tests.
|
|
15
|
+
|
|
16
|
+
The following Capybara methods are now available directly without having to use the `page` method:
|
|
17
|
+
|
|
18
|
+
* [`all`](https://rubydoc.info/github/teamcapybara/capybara/Capybara%2FNode%2FFinders:all)
|
|
19
|
+
* [`first`](https://rubydoc.info/github/teamcapybara/capybara/Capybara%2FNode%2FFinders:first)
|
|
20
|
+
* [`text`](https://rubydoc.info/github/teamcapybara/capybara/Capybara%2FNode%2FSimple:text)
|
|
21
|
+
* [`find`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find)
|
|
22
|
+
* [`find_all`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find_all)
|
|
23
|
+
* [`find_button`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find_button)
|
|
24
|
+
* [`find_by_id`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find_by_id)
|
|
25
|
+
* [`find_field`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find_field)
|
|
26
|
+
* [`find_link`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find_link)
|
|
27
|
+
* [`has_content?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_content%3F)
|
|
28
|
+
* [`has_text?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_text%3F)
|
|
29
|
+
* [`has_css?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_css%3F)
|
|
30
|
+
* [`has_no_content?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_content%3F)
|
|
31
|
+
* [`has_no_text?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_text%3F)
|
|
32
|
+
* [`has_no_css?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_css%3F)
|
|
33
|
+
* [`has_no_xpath?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_xpath%3F)
|
|
34
|
+
* [`has_xpath?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_xpath%3F)
|
|
35
|
+
* [`has_link?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_link%3F)
|
|
36
|
+
* [`has_no_link?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_link%3F)
|
|
37
|
+
* [`has_button?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_button%3F)
|
|
38
|
+
* [`has_no_button?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_button%3F)
|
|
39
|
+
* [`has_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_field%3F)
|
|
40
|
+
* [`has_no_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_field%3F)
|
|
41
|
+
* [`has_checked_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_checked_field%3F)
|
|
42
|
+
* [`has_unchecked_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_unchecked_field%3F)
|
|
43
|
+
* [`has_no_table?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_table%3F)
|
|
44
|
+
* [`has_table?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_table%3F)
|
|
45
|
+
* [`has_select?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_select%3F)
|
|
46
|
+
* [`has_no_select?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_select%3F)
|
|
47
|
+
* [`has_selector?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_selector%3F)
|
|
48
|
+
* [`has_no_selector?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_selector%3F)
|
|
49
|
+
* [`has_no_checked_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_checked_field%3F)
|
|
50
|
+
* [`has_no_unchecked_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_unchecked_field%3F)
|
|
51
|
+
|
|
52
|
+
* Add support for `within*` Capybara DLS methods:
|
|
53
|
+
|
|
54
|
+
* [`within`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FSession:within)
|
|
55
|
+
* [`within_element`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FSession:within)
|
|
56
|
+
* [`within_fieldset`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FSession:within_fieldset)
|
|
57
|
+
* [`within_table`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FSession:within_table)
|
|
58
|
+
|
|
59
|
+
*Jacob Carlborg*
|
|
60
|
+
|
|
61
|
+
## 2.58.0
|
|
62
|
+
|
|
63
|
+
* Switch to `standardrb`.
|
|
64
|
+
|
|
65
|
+
*Joel Hawksley*
|
|
66
|
+
|
|
67
|
+
* Add BootrAils article to resources.
|
|
68
|
+
|
|
69
|
+
*Joel Hawksley*
|
|
70
|
+
|
|
71
|
+
* Add @boardfish and @spone as maintainers.
|
|
72
|
+
|
|
73
|
+
*Joel Hawksley*, *Cameron Dutro*, *Blake Williams*
|
|
74
|
+
|
|
75
|
+
* Re-compile updated, inherited templates when class caching is disabled.
|
|
76
|
+
|
|
77
|
+
*Patrick Arnett*
|
|
78
|
+
|
|
79
|
+
* Add the latest version to the docs index.
|
|
80
|
+
* Improve the docs: add the versions various features were introduced in.
|
|
81
|
+
|
|
82
|
+
*Hans Lemuet*
|
|
83
|
+
|
|
84
|
+
* Update docs to reflect lack of block content support in controllers.
|
|
85
|
+
|
|
86
|
+
*Joel Hawksley*
|
|
87
|
+
|
|
88
|
+
* Prevent adding duplicates to `autoload_paths`.
|
|
89
|
+
|
|
90
|
+
*Thomas Hutterer*
|
|
91
|
+
|
|
92
|
+
* Add FreeAgent to list of companies using ViewComponent.
|
|
93
|
+
|
|
94
|
+
*Simon Fish*
|
|
95
|
+
|
|
96
|
+
* Include polymorphic slots in `ViewComponent::Base` by default.
|
|
97
|
+
|
|
98
|
+
*Cameron Dutro*
|
|
99
|
+
|
|
100
|
+
* Add per-component config option for stripping newlines from templates before compilation.
|
|
101
|
+
|
|
102
|
+
*Cameron Dutro*
|
|
103
|
+
|
|
104
|
+
* Add link to article by Matouš Borák.
|
|
105
|
+
|
|
106
|
+
*Joel Hawksley*
|
|
107
|
+
|
|
108
|
+
## 2.57.1
|
|
109
|
+
|
|
110
|
+
* Fix issue causing `NoMethodError`s when calling helper methods from components rendered as part of a collection.
|
|
111
|
+
* Fix syntax error in the ERB example in the polymorphic slots docs.
|
|
112
|
+
|
|
113
|
+
*Cameron Dutro*
|
|
114
|
+
|
|
12
115
|
## 2.57.0
|
|
13
116
|
|
|
14
117
|
* Add missing `require` for `Translatable` module in `Base`.
|
|
@@ -36,10 +36,10 @@ module ViewComponent
|
|
|
36
36
|
|
|
37
37
|
def stimulus_controller
|
|
38
38
|
if options["stimulus"]
|
|
39
|
-
File.join(destination_directory, destination_file_name)
|
|
40
|
-
sub("#{component_path}/", "")
|
|
41
|
-
|
|
42
|
-
gsub("/", "--")
|
|
39
|
+
File.join(destination_directory, destination_file_name)
|
|
40
|
+
.sub("#{component_path}/", "")
|
|
41
|
+
.tr("_", "-")
|
|
42
|
+
.gsub("/", "--")
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
data/lib/view_component/base.rb
CHANGED
|
@@ -16,6 +16,7 @@ module ViewComponent
|
|
|
16
16
|
class Base < ActionView::Base
|
|
17
17
|
include ActiveSupport::Configurable
|
|
18
18
|
include ViewComponent::ContentAreas
|
|
19
|
+
include ViewComponent::PolymorphicSlots
|
|
19
20
|
include ViewComponent::Previewable
|
|
20
21
|
include ViewComponent::SlotableV2
|
|
21
22
|
include ViewComponent::Translatable
|
|
@@ -31,8 +32,25 @@ module ViewComponent
|
|
|
31
32
|
class_attribute :content_areas
|
|
32
33
|
self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2
|
|
33
34
|
|
|
35
|
+
# Config option that strips trailing whitespace in templates before compiling them.
|
|
36
|
+
class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false
|
|
37
|
+
self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2
|
|
38
|
+
|
|
34
39
|
attr_accessor :__vc_original_view_context
|
|
35
40
|
|
|
41
|
+
# Components render in their own view context. Helpers and other functionality
|
|
42
|
+
# require a reference to the original Rails view context, an instance of
|
|
43
|
+
# `ActionView::Base`. Use this method to set a reference to the original
|
|
44
|
+
# view context. Objects that implement this method will render in the component's
|
|
45
|
+
# view context, while objects that don't will render in the original view context
|
|
46
|
+
# so helpers, etc work as expected.
|
|
47
|
+
#
|
|
48
|
+
# @param view_context [ActionView::Base] The original view context.
|
|
49
|
+
# @return [void]
|
|
50
|
+
def set_original_view_context(view_context)
|
|
51
|
+
self.__vc_original_view_context = view_context
|
|
52
|
+
end
|
|
53
|
+
|
|
36
54
|
# EXPERIMENTAL: This API is experimental and may be removed at any time.
|
|
37
55
|
# Hook for allowing components to do work as part of the compilation process.
|
|
38
56
|
#
|
|
@@ -114,24 +132,27 @@ module ViewComponent
|
|
|
114
132
|
@current_template = old_current_template
|
|
115
133
|
end
|
|
116
134
|
|
|
135
|
+
# @private
|
|
117
136
|
def perform_render
|
|
118
137
|
render_template_for(@__vc_variant).to_s + _output_postamble
|
|
119
138
|
end
|
|
120
139
|
|
|
121
140
|
# Subclass components that call `super` inside their template code will cause a
|
|
122
|
-
# double render if they
|
|
141
|
+
# double render if they emit the result:
|
|
123
142
|
#
|
|
143
|
+
# ```erb
|
|
124
144
|
# <%= super %> # double-renders
|
|
125
|
-
#
|
|
126
145
|
# <% super %> # does not double-render
|
|
146
|
+
# ```
|
|
127
147
|
#
|
|
128
|
-
# Calls `super`, returning nil to avoid rendering the result twice.
|
|
148
|
+
# Calls `super`, returning `nil` to avoid rendering the result twice.
|
|
129
149
|
def render_parent
|
|
130
150
|
mtd = @__vc_variant ? "call_#{@__vc_variant}" : "call"
|
|
131
151
|
method(mtd).super_method.call
|
|
132
152
|
nil
|
|
133
153
|
end
|
|
134
154
|
|
|
155
|
+
# @private
|
|
135
156
|
# :nocov:
|
|
136
157
|
def render_template_for(variant = nil)
|
|
137
158
|
# Force compilation here so the compiler always redefines render_template_for.
|
|
@@ -173,7 +194,8 @@ module ViewComponent
|
|
|
173
194
|
end
|
|
174
195
|
|
|
175
196
|
# @private
|
|
176
|
-
def initialize(*)
|
|
197
|
+
def initialize(*)
|
|
198
|
+
end
|
|
177
199
|
|
|
178
200
|
# Re-use original view_context if we're not rendering a component.
|
|
179
201
|
#
|
|
@@ -183,11 +205,8 @@ module ViewComponent
|
|
|
183
205
|
#
|
|
184
206
|
# @private
|
|
185
207
|
def render(options = {}, args = {}, &block)
|
|
186
|
-
if options.respond_to?(:
|
|
187
|
-
|
|
188
|
-
options.__vc_original_view_context = __vc_original_view_context
|
|
189
|
-
end
|
|
190
|
-
|
|
208
|
+
if options.respond_to?(:set_original_view_context)
|
|
209
|
+
options.set_original_view_context(self.__vc_original_view_context)
|
|
191
210
|
super
|
|
192
211
|
else
|
|
193
212
|
__vc_original_view_context.render(options, args, &block)
|
|
@@ -304,7 +323,9 @@ module ViewComponent
|
|
|
304
323
|
|
|
305
324
|
# Set the controller used for testing components:
|
|
306
325
|
#
|
|
307
|
-
#
|
|
326
|
+
# ```ruby
|
|
327
|
+
# config.view_component.test_controller = "MyTestController"
|
|
328
|
+
# ```
|
|
308
329
|
#
|
|
309
330
|
# Defaults to ApplicationController. Can also be configured on a per-test
|
|
310
331
|
# basis using `with_controller_class`.
|
|
@@ -314,13 +335,17 @@ module ViewComponent
|
|
|
314
335
|
|
|
315
336
|
# Set if render monkey patches should be included or not in Rails <6.1:
|
|
316
337
|
#
|
|
317
|
-
#
|
|
338
|
+
# ```ruby
|
|
339
|
+
# config.view_component.render_monkey_patch_enabled = false
|
|
340
|
+
# ```
|
|
318
341
|
#
|
|
319
342
|
mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
|
|
320
343
|
|
|
321
344
|
# Path for component files
|
|
322
345
|
#
|
|
323
|
-
#
|
|
346
|
+
# ```ruby
|
|
347
|
+
# config.view_component.view_component_path = "app/my_components"
|
|
348
|
+
# ```
|
|
324
349
|
#
|
|
325
350
|
# Defaults to `app/components`.
|
|
326
351
|
#
|
|
@@ -328,7 +353,9 @@ module ViewComponent
|
|
|
328
353
|
|
|
329
354
|
# Parent class for generated components
|
|
330
355
|
#
|
|
331
|
-
#
|
|
356
|
+
# ```ruby
|
|
357
|
+
# config.view_component.component_parent_class = "MyBaseComponent"
|
|
358
|
+
# ```
|
|
332
359
|
#
|
|
333
360
|
# Defaults to nil. If this is falsy, generators will use
|
|
334
361
|
# "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
|
|
@@ -344,25 +371,33 @@ module ViewComponent
|
|
|
344
371
|
#
|
|
345
372
|
# Always generate a component with a sidecar directory:
|
|
346
373
|
#
|
|
347
|
-
#
|
|
374
|
+
# ```ruby
|
|
375
|
+
# config.view_component.generate.sidecar = true
|
|
376
|
+
# ```
|
|
348
377
|
#
|
|
349
378
|
# #### #stimulus_controller
|
|
350
379
|
#
|
|
351
380
|
# Always generate a Stimulus controller alongside the component:
|
|
352
381
|
#
|
|
353
|
-
#
|
|
382
|
+
# ```ruby
|
|
383
|
+
# config.view_component.generate.stimulus_controller = true
|
|
384
|
+
# ```
|
|
354
385
|
#
|
|
355
386
|
# #### #locale
|
|
356
387
|
#
|
|
357
388
|
# Always generate translations file alongside the component:
|
|
358
389
|
#
|
|
359
|
-
#
|
|
390
|
+
# ```ruby
|
|
391
|
+
# config.view_component.generate.locale = true
|
|
392
|
+
# ```
|
|
360
393
|
#
|
|
361
394
|
# #### #distinct_locale_files
|
|
362
395
|
#
|
|
363
396
|
# Always generate as many translations files as available locales:
|
|
364
397
|
#
|
|
365
|
-
#
|
|
398
|
+
# ```ruby
|
|
399
|
+
# config.view_component.generate.distinct_locale_files = true
|
|
400
|
+
# ```
|
|
366
401
|
#
|
|
367
402
|
# One file will be generated for each configured `I18n.available_locales`,
|
|
368
403
|
# falling back to `[:en]` when no `available_locales` is defined.
|
|
@@ -371,7 +406,9 @@ module ViewComponent
|
|
|
371
406
|
#
|
|
372
407
|
# Always generate preview alongside the component:
|
|
373
408
|
#
|
|
374
|
-
#
|
|
409
|
+
# ```ruby
|
|
410
|
+
# config.view_component.generate.preview = true
|
|
411
|
+
# ```
|
|
375
412
|
#
|
|
376
413
|
# Defaults to `false`.
|
|
377
414
|
mattr_accessor :generate, instance_writer: false, default: ActiveSupport::OrderedOptions.new(false)
|
|
@@ -425,7 +462,9 @@ module ViewComponent
|
|
|
425
462
|
|
|
426
463
|
# Render a component for each element in a collection ([documentation](/guide/collections)):
|
|
427
464
|
#
|
|
428
|
-
#
|
|
465
|
+
# ```ruby
|
|
466
|
+
# render(ProductsComponent.with_collection(@products, foo: :bar))
|
|
467
|
+
# ```
|
|
429
468
|
#
|
|
430
469
|
# @param collection [Enumerable] A list of items to pass the ViewComponent one at a time.
|
|
431
470
|
# @param args [Arguments] Arguments to pass to the ViewComponent every time.
|
|
@@ -521,13 +560,35 @@ module ViewComponent
|
|
|
521
560
|
|
|
522
561
|
# Set the parameter name used when rendering elements of a collection ([documentation](/guide/collections)):
|
|
523
562
|
#
|
|
524
|
-
#
|
|
563
|
+
# ```ruby
|
|
564
|
+
# with_collection_parameter :item
|
|
565
|
+
# ```
|
|
525
566
|
#
|
|
526
567
|
# @param parameter [Symbol] The parameter name used when rendering elements of a collection.
|
|
527
568
|
def with_collection_parameter(parameter)
|
|
528
569
|
@provided_collection_parameter = parameter
|
|
529
570
|
end
|
|
530
571
|
|
|
572
|
+
# Strips trailing whitespace from templates before compiling them.
|
|
573
|
+
#
|
|
574
|
+
# ```ruby
|
|
575
|
+
# class MyComponent < ViewComponent::Base
|
|
576
|
+
# strip_trailing_whitespace
|
|
577
|
+
# end
|
|
578
|
+
# ```
|
|
579
|
+
#
|
|
580
|
+
# @param value [Boolean] Whether or not to strip newlines.
|
|
581
|
+
def strip_trailing_whitespace(value = true)
|
|
582
|
+
self.__vc_strip_trailing_whitespace = value
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
# Whether trailing whitespace will be stripped before compilation.
|
|
586
|
+
#
|
|
587
|
+
# @return [Boolean]
|
|
588
|
+
def strip_trailing_whitespace?
|
|
589
|
+
__vc_strip_trailing_whitespace
|
|
590
|
+
end
|
|
591
|
+
|
|
531
592
|
# Ensure the component initializer accepts the
|
|
532
593
|
# collection parameter. By default, we don't
|
|
533
594
|
# validate that the default parameter name
|
|
@@ -575,11 +636,7 @@ module ViewComponent
|
|
|
575
636
|
|
|
576
637
|
# @private
|
|
577
638
|
def collection_parameter
|
|
578
|
-
|
|
579
|
-
provided_collection_parameter
|
|
580
|
-
else
|
|
581
|
-
name && name.demodulize.underscore.chomp("_component").to_sym
|
|
582
|
-
end
|
|
639
|
+
provided_collection_parameter || name && name.demodulize.underscore.chomp("_component").to_sym
|
|
583
640
|
end
|
|
584
641
|
|
|
585
642
|
# @private
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ViewComponent
|
|
4
|
+
# This is a simpler version of {Capybara::Session}.
|
|
5
|
+
#
|
|
6
|
+
# It only includes {Capybara::Node::Finders}, {Capybara::Node::Matchers},
|
|
7
|
+
# {#within} and {#within_element}. It is useful in that it does not require a
|
|
8
|
+
# session, an application or a driver, but can still use Capybara's finders
|
|
9
|
+
# and matchers on any string that contains HTML.
|
|
10
|
+
class CapybaraSimpleSession
|
|
11
|
+
# Most of the code in this class is shamelessly stolen from the
|
|
12
|
+
# {Capybara::Session} class in the Capybara gem
|
|
13
|
+
# (https://github.com/teamcapybara/capybara/blob/e704d00879fb1d1e1a0cc01e04c101bcd8af4a68/lib/capybara/session.rb#L38).
|
|
14
|
+
|
|
15
|
+
NODE_METHODS = %i[
|
|
16
|
+
all
|
|
17
|
+
first
|
|
18
|
+
text
|
|
19
|
+
|
|
20
|
+
find
|
|
21
|
+
find_all
|
|
22
|
+
find_button
|
|
23
|
+
find_by_id
|
|
24
|
+
find_field
|
|
25
|
+
find_link
|
|
26
|
+
|
|
27
|
+
has_content?
|
|
28
|
+
has_text?
|
|
29
|
+
has_css?
|
|
30
|
+
has_no_content?
|
|
31
|
+
has_no_text?
|
|
32
|
+
has_no_css?
|
|
33
|
+
has_no_xpath?
|
|
34
|
+
has_xpath?
|
|
35
|
+
has_link?
|
|
36
|
+
has_no_link?
|
|
37
|
+
has_button?
|
|
38
|
+
has_no_button?
|
|
39
|
+
has_field?
|
|
40
|
+
has_no_field?
|
|
41
|
+
has_checked_field?
|
|
42
|
+
has_unchecked_field?
|
|
43
|
+
has_no_table?
|
|
44
|
+
has_table?
|
|
45
|
+
has_select?
|
|
46
|
+
has_no_select?
|
|
47
|
+
has_selector?
|
|
48
|
+
has_no_selector?
|
|
49
|
+
has_no_checked_field?
|
|
50
|
+
has_no_unchecked_field?
|
|
51
|
+
|
|
52
|
+
assert_selector
|
|
53
|
+
assert_no_selector
|
|
54
|
+
assert_all_of_selectors
|
|
55
|
+
assert_none_of_selectors
|
|
56
|
+
assert_any_of_selectors
|
|
57
|
+
assert_text
|
|
58
|
+
assert_no_text
|
|
59
|
+
].freeze
|
|
60
|
+
|
|
61
|
+
private_constant :NODE_METHODS
|
|
62
|
+
|
|
63
|
+
SESSION_METHODS = %i[within within_element within_fieldset within_table].freeze
|
|
64
|
+
|
|
65
|
+
private_constant :SESSION_METHODS
|
|
66
|
+
|
|
67
|
+
DSL_METHODS = (NODE_METHODS + SESSION_METHODS).freeze
|
|
68
|
+
|
|
69
|
+
# Stolen from: https://github.com/teamcapybara/capybara/blob/e704d00879fb1d1e1a0cc01e04c101bcd8af4a68/lib/capybara/session.rb#L767-L774.
|
|
70
|
+
NODE_METHODS.each do |method|
|
|
71
|
+
if RUBY_VERSION >= "2.7"
|
|
72
|
+
class_eval <<~METHOD, __FILE__, __LINE__ + 1
|
|
73
|
+
def #{method}(...)
|
|
74
|
+
current_scope.#{method}(...)
|
|
75
|
+
end
|
|
76
|
+
METHOD
|
|
77
|
+
else
|
|
78
|
+
define_method method do |*args, &block|
|
|
79
|
+
current_scope.send(method, *args, &block)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Initializes the receiver with the given string of HTML.
|
|
85
|
+
#
|
|
86
|
+
# @param html [String] the HTML to create the session out of
|
|
87
|
+
def initialize(html)
|
|
88
|
+
@document = Capybara::Node::Simple.new(html)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# (see Capybara::Session#within)
|
|
92
|
+
def within(*args, **kw_args)
|
|
93
|
+
new_scope = args.first.respond_to?(:to_capybara_node) ? args.first.to_capybara_node : find(*args, **kw_args)
|
|
94
|
+
begin
|
|
95
|
+
scopes.push(new_scope)
|
|
96
|
+
yield if block_given?
|
|
97
|
+
ensure
|
|
98
|
+
scopes.pop
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# (see Capybara::Session#within_element)
|
|
103
|
+
alias_method :within_element, :within
|
|
104
|
+
|
|
105
|
+
# (see Capybara::Session#within_fieldset)
|
|
106
|
+
def within_fieldset(locator, &block)
|
|
107
|
+
within(:fieldset, locator, &block)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# (see Capybara::Session#within_table)
|
|
111
|
+
def within_table(locator, &block)
|
|
112
|
+
within(:table, locator, &block)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# (see Capybara::Node::Element#native)
|
|
116
|
+
def native
|
|
117
|
+
current_scope.native
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
attr_reader :document
|
|
123
|
+
|
|
124
|
+
def scopes
|
|
125
|
+
@scopes ||= [nil]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def current_scope
|
|
129
|
+
scopes.last.presence || document
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -10,10 +10,17 @@ module ViewComponent
|
|
|
10
10
|
delegate :format, to: :component
|
|
11
11
|
delegate :size, to: :@collection
|
|
12
12
|
|
|
13
|
+
attr_accessor :__vc_original_view_context
|
|
14
|
+
|
|
15
|
+
def set_original_view_context(view_context)
|
|
16
|
+
self.__vc_original_view_context = view_context
|
|
17
|
+
end
|
|
18
|
+
|
|
13
19
|
def render_in(view_context, &block)
|
|
14
20
|
components.map do |component|
|
|
21
|
+
component.set_original_view_context(__vc_original_view_context)
|
|
15
22
|
component.render_in(view_context, &block)
|
|
16
|
-
end.join.html_safe
|
|
23
|
+
end.join.html_safe
|
|
17
24
|
end
|
|
18
25
|
|
|
19
26
|
def components
|
|
@@ -54,7 +61,7 @@ module ViewComponent
|
|
|
54
61
|
end
|
|
55
62
|
|
|
56
63
|
def component_options(item, iterator)
|
|
57
|
-
item_options = {
|
|
64
|
+
item_options = {component.collection_parameter => item}
|
|
58
65
|
item_options[component.collection_counter_parameter] = iterator.index + 1 if component.counter_argument_present?
|
|
59
66
|
item_options[component.collection_iteration_parameter] = iterator.dup if component.iteration_argument_present?
|
|
60
67
|
|
|
@@ -31,6 +31,8 @@ module ViewComponent
|
|
|
31
31
|
return if compiled? && !force
|
|
32
32
|
return if component_class == ViewComponent::Base
|
|
33
33
|
|
|
34
|
+
component_class.superclass.compile(raise_errors: raise_errors) if should_compile_superclass?
|
|
35
|
+
|
|
34
36
|
with_lock do
|
|
35
37
|
subclass_instance_methods = component_class.instance_methods(false)
|
|
36
38
|
|
|
@@ -68,11 +70,13 @@ module ViewComponent
|
|
|
68
70
|
component_class.send(:remove_method, method_name.to_sym)
|
|
69
71
|
end
|
|
70
72
|
|
|
73
|
+
# rubocop:disable Style/EvalWithLocation
|
|
71
74
|
component_class.class_eval <<-RUBY, template[:path], 0
|
|
72
75
|
def #{method_name}
|
|
73
76
|
#{compiled_template(template[:path])}
|
|
74
77
|
end
|
|
75
78
|
RUBY
|
|
79
|
+
# rubocop:enable Style/EvalWithLocation
|
|
76
80
|
end
|
|
77
81
|
|
|
78
82
|
define_render_template_for
|
|
@@ -151,15 +155,15 @@ module ViewComponent
|
|
|
151
155
|
end
|
|
152
156
|
|
|
153
157
|
invalid_variants =
|
|
154
|
-
templates
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
templates
|
|
159
|
+
.group_by { |template| template[:variant] }
|
|
160
|
+
.map { |variant, grouped| variant if grouped.length > 1 }
|
|
161
|
+
.compact
|
|
162
|
+
.sort
|
|
159
163
|
|
|
160
164
|
unless invalid_variants.empty?
|
|
161
165
|
errors <<
|
|
162
|
-
"More than one template found for #{
|
|
166
|
+
"More than one template found for #{"variant".pluralize(invalid_variants.count)} " \
|
|
163
167
|
"#{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{component_class}. " \
|
|
164
168
|
"There can only be one template file per variant."
|
|
165
169
|
end
|
|
@@ -177,8 +181,8 @@ module ViewComponent
|
|
|
177
181
|
count = duplicate_template_file_and_inline_variant_calls.count
|
|
178
182
|
|
|
179
183
|
errors <<
|
|
180
|
-
"Template #{
|
|
181
|
-
"found for #{
|
|
184
|
+
"Template #{"file".pluralize(count)} and inline render #{"method".pluralize(count)} " \
|
|
185
|
+
"found for #{"variant".pluralize(count)} " \
|
|
182
186
|
"#{duplicate_template_file_and_inline_variant_calls.map { |v| "'#{v}'" }.to_sentence} " \
|
|
183
187
|
"in #{component_class}. " \
|
|
184
188
|
"There can only be a template file or inline render method per variant."
|
|
@@ -236,8 +240,9 @@ module ViewComponent
|
|
|
236
240
|
end
|
|
237
241
|
|
|
238
242
|
def compiled_template(file_path)
|
|
239
|
-
handler = ActionView::Template.handler_for_extension(File.extname(file_path).
|
|
243
|
+
handler = ActionView::Template.handler_for_extension(File.extname(file_path).delete("."))
|
|
240
244
|
template = File.read(file_path)
|
|
245
|
+
template.rstrip! if component_class.strip_trailing_whitespace?
|
|
241
246
|
|
|
242
247
|
if handler.method(:call).parameters.length > 1
|
|
243
248
|
handler.call(component_class, template)
|
|
@@ -259,5 +264,14 @@ module ViewComponent
|
|
|
259
264
|
"call"
|
|
260
265
|
end
|
|
261
266
|
end
|
|
267
|
+
|
|
268
|
+
def should_compile_superclass?
|
|
269
|
+
development? &&
|
|
270
|
+
templates.empty? &&
|
|
271
|
+
!(
|
|
272
|
+
component_class.instance_methods(false).include?(:call) ||
|
|
273
|
+
component_class.private_instance_methods(false).include?(:call)
|
|
274
|
+
)
|
|
275
|
+
end
|
|
262
276
|
end
|
|
263
277
|
end
|
|
@@ -28,7 +28,7 @@ module ViewComponent
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def types
|
|
31
|
-
" → [#{@method.tag(:return).types.join(
|
|
31
|
+
" → [#{@method.tag(:return).types.join(",")}]" if @method.tag(:return)&.types && show_types?
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def signature_or_name
|
|
@@ -77,7 +77,8 @@ module ViewComponent
|
|
|
77
77
|
options = app.config.view_component
|
|
78
78
|
|
|
79
79
|
if options.show_previews && !options.preview_paths.empty?
|
|
80
|
-
ActiveSupport::Dependencies.autoload_paths
|
|
80
|
+
paths_to_add = options.preview_paths - ActiveSupport::Dependencies.autoload_paths
|
|
81
|
+
ActiveSupport::Dependencies.autoload_paths.concat(paths_to_add) if paths_to_add.any?
|
|
81
82
|
end
|
|
82
83
|
end
|
|
83
84
|
|
|
@@ -133,10 +134,10 @@ module ViewComponent
|
|
|
133
134
|
|
|
134
135
|
initializer "compiler mode" do |app|
|
|
135
136
|
ViewComponent::Compiler.mode = if Rails.env.development? || Rails.env.test?
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
ViewComponent::Compiler::DEVELOPMENT_MODE
|
|
138
|
+
else
|
|
139
|
+
ViewComponent::Compiler::PRODUCTION_MODE
|
|
140
|
+
end
|
|
140
141
|
end
|
|
141
142
|
|
|
142
143
|
config.after_initialize do |app|
|
|
@@ -34,13 +34,14 @@ module ViewComponent
|
|
|
34
34
|
def with_output_buffer(buf = nil)
|
|
35
35
|
unless buf
|
|
36
36
|
buf = ActionView::OutputBuffer.new
|
|
37
|
+
# rubocop:disable Style/SafeNavigation
|
|
37
38
|
if output_buffer && output_buffer.respond_to?(:encoding)
|
|
38
39
|
buf.force_encoding(output_buffer.encoding)
|
|
39
40
|
end
|
|
41
|
+
# rubocop:enable Style/SafeNavigation
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
output_buffer.push(buf)
|
|
43
|
-
result = nil
|
|
44
45
|
|
|
45
46
|
begin
|
|
46
47
|
yield
|
|
@@ -65,13 +66,13 @@ module ViewComponent
|
|
|
65
66
|
def with_output_buffer(buf = nil)
|
|
66
67
|
unless buf
|
|
67
68
|
buf = ActionView::OutputBuffer.new
|
|
69
|
+
# rubocop:disable Style/SafeNavigation
|
|
68
70
|
if @output_buffer && @output_buffer.respond_to?(:encoding)
|
|
69
71
|
buf.force_encoding(@output_buffer.encoding)
|
|
70
72
|
end
|
|
73
|
+
# rubocop:enable Style/SafeNavigation
|
|
71
74
|
end
|
|
72
75
|
|
|
73
|
-
result = nil
|
|
74
|
-
|
|
75
76
|
if @output_buffer.is_a?(OutputBufferStack)
|
|
76
77
|
@output_buffer.push(buf)
|
|
77
78
|
|
|
@@ -5,6 +5,17 @@ module ViewComponent
|
|
|
5
5
|
# In older rails versions, using a concern isn't a good idea here because they appear to not work with
|
|
6
6
|
# Module#prepend and class methods.
|
|
7
7
|
def self.included(base)
|
|
8
|
+
if base != ViewComponent::Base
|
|
9
|
+
# :nocov:
|
|
10
|
+
location = Kernel.caller_locations(1, 1)[0]
|
|
11
|
+
|
|
12
|
+
warn(
|
|
13
|
+
"warning: ViewComponent::PolymorphicSlots is now included in ViewComponent::Base by default "\
|
|
14
|
+
"and can be removed from #{location.path}:#{location.lineno}"
|
|
15
|
+
)
|
|
16
|
+
# :nocov:
|
|
17
|
+
end
|
|
18
|
+
|
|
8
19
|
base.singleton_class.prepend(ClassMethods)
|
|
9
20
|
base.include(InstanceMethods)
|
|
10
21
|
end
|
|
@@ -45,8 +56,12 @@ module ViewComponent
|
|
|
45
56
|
"#{slot_name}_#{poly_type}"
|
|
46
57
|
end
|
|
47
58
|
|
|
48
|
-
# Deprecated: Will be removed in 3.0
|
|
49
59
|
define_method(setter_name) do |*args, &block|
|
|
60
|
+
ViewComponent::Deprecation.warn(
|
|
61
|
+
"polymorphic slot setters like `#{setter_name}` are deprecated and will be removed in"\
|
|
62
|
+
"ViewComponent v3.0.0.\n\nUse `with_#{setter_name}` instead."
|
|
63
|
+
)
|
|
64
|
+
|
|
50
65
|
set_polymorphic_slot(slot_name, poly_type, *args, &block)
|
|
51
66
|
end
|
|
52
67
|
ruby2_keywords(setter_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
|
@@ -57,7 +72,7 @@ module ViewComponent
|
|
|
57
72
|
ruby2_keywords(:"with_#{setter_name}") if respond_to?(:ruby2_keywords, true)
|
|
58
73
|
end
|
|
59
74
|
|
|
60
|
-
|
|
75
|
+
registered_slots[slot_name] = {
|
|
61
76
|
collection: collection,
|
|
62
77
|
renderable_hash: renderable_hash
|
|
63
78
|
}
|
|
@@ -14,7 +14,7 @@ module ViewComponent # :nodoc:
|
|
|
14
14
|
block: block,
|
|
15
15
|
component: component,
|
|
16
16
|
locals: {},
|
|
17
|
-
template: "view_components/preview"
|
|
17
|
+
template: "view_components/preview"
|
|
18
18
|
}
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -66,10 +66,12 @@ module ViewComponent # :nodoc:
|
|
|
66
66
|
name.chomp("Preview").underscore
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# rubocop:disable Style/TrivialAccessors
|
|
69
70
|
# Setter for layout name.
|
|
70
71
|
def layout(layout_name)
|
|
71
72
|
@layout = layout_name
|
|
72
73
|
end
|
|
74
|
+
# rubocop:enable Style/TrivialAccessors
|
|
73
75
|
|
|
74
76
|
# Returns the relative path (from preview_path) to the preview example template if the template exists
|
|
75
77
|
def preview_example_template_path(example)
|
|
@@ -87,15 +89,15 @@ module ViewComponent # :nodoc:
|
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
|
|
90
|
-
Pathname.new(path)
|
|
91
|
-
relative_path_from(Pathname.new(preview_path))
|
|
92
|
-
to_s
|
|
93
|
-
sub(/\..*$/, "")
|
|
92
|
+
Pathname.new(path)
|
|
93
|
+
.relative_path_from(Pathname.new(preview_path))
|
|
94
|
+
.to_s
|
|
95
|
+
.sub(/\..*$/, "")
|
|
94
96
|
end
|
|
95
97
|
|
|
96
98
|
# Returns the method body for the example from the preview file.
|
|
97
99
|
def preview_source(example)
|
|
98
|
-
source =
|
|
100
|
+
source = instance_method(example.to_sym).source.split("\n")
|
|
99
101
|
source[1...(source.size - 1)].join("\n")
|
|
100
102
|
end
|
|
101
103
|
|
|
@@ -4,7 +4,7 @@ module ViewComponent
|
|
|
4
4
|
module RenderingMonkeyPatch # :nodoc:
|
|
5
5
|
def render(options = {}, args = {})
|
|
6
6
|
if options.respond_to?(:render_in)
|
|
7
|
-
self.response_body = options.render_in(
|
|
7
|
+
self.response_body = options.render_in(view_context)
|
|
8
8
|
else
|
|
9
9
|
super
|
|
10
10
|
end
|
|
@@ -30,7 +30,7 @@ module ViewComponent
|
|
|
30
30
|
|
|
31
31
|
slot_names.each do |slot_name|
|
|
32
32
|
# Ensure slot_name isn't already declared
|
|
33
|
-
if
|
|
33
|
+
if slots.key?(slot_name)
|
|
34
34
|
raise ArgumentError.new("#{slot_name} slot declared multiple times")
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -73,7 +73,7 @@ module ViewComponent
|
|
|
73
73
|
class_name = "ViewComponent::Slot" unless class_name.present?
|
|
74
74
|
|
|
75
75
|
# Register the slot on the component
|
|
76
|
-
|
|
76
|
+
slots[slot_name] = {
|
|
77
77
|
class_name: class_name,
|
|
78
78
|
instance_variable_name: instance_variable_name,
|
|
79
79
|
collection: collection
|
|
@@ -84,7 +84,7 @@ module ViewComponent
|
|
|
84
84
|
def inherited(child)
|
|
85
85
|
# Clone slot configuration into child class
|
|
86
86
|
# see #test_slots_pollution
|
|
87
|
-
child.slots =
|
|
87
|
+
child.slots = slots.clone
|
|
88
88
|
|
|
89
89
|
super
|
|
90
90
|
end
|
|
@@ -106,7 +106,7 @@ module ViewComponent
|
|
|
106
106
|
#
|
|
107
107
|
def slot(slot_name, **args, &block)
|
|
108
108
|
# Raise ArgumentError if `slot` doesn't exist
|
|
109
|
-
unless slots.
|
|
109
|
+
unless slots.key?(slot_name)
|
|
110
110
|
raise ArgumentError.new "Unknown slot '#{slot_name}' - expected one of '#{slots.keys}'"
|
|
111
111
|
end
|
|
112
112
|
|
|
@@ -123,8 +123,7 @@ module ViewComponent
|
|
|
123
123
|
slot_instance = args.present? ? slot_class.new(**args) : slot_class.new
|
|
124
124
|
|
|
125
125
|
# Capture block and assign to slot_instance#content
|
|
126
|
-
|
|
127
|
-
slot_instance.content = view_context.capture(&block).to_s.strip.html_safe if block_given?
|
|
126
|
+
slot_instance.content = view_context.capture(&block).to_s.strip.html_safe if block
|
|
128
127
|
|
|
129
128
|
if slot[:collection]
|
|
130
129
|
# Initialize instance variable as an empty array
|
|
@@ -9,7 +9,7 @@ module ViewComponent
|
|
|
9
9
|
|
|
10
10
|
RESERVED_NAMES = {
|
|
11
11
|
singular: %i[content render].freeze,
|
|
12
|
-
plural: %i[contents renders].freeze
|
|
12
|
+
plural: %i[contents renders].freeze
|
|
13
13
|
}.freeze
|
|
14
14
|
|
|
15
15
|
# Setup component slot state
|
|
@@ -190,20 +190,20 @@ module ViewComponent
|
|
|
190
190
|
# Clone slot configuration into child class
|
|
191
191
|
# see #test_slots_pollution
|
|
192
192
|
def inherited(child)
|
|
193
|
-
child.registered_slots =
|
|
193
|
+
child.registered_slots = registered_slots.clone
|
|
194
194
|
super
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
private
|
|
198
198
|
|
|
199
199
|
def register_slot(slot_name, **kwargs)
|
|
200
|
-
|
|
200
|
+
registered_slots[slot_name] = define_slot(slot_name, **kwargs)
|
|
201
201
|
end
|
|
202
202
|
|
|
203
203
|
def define_slot(slot_name, collection:, callable:)
|
|
204
204
|
# Setup basic slot data
|
|
205
205
|
slot = {
|
|
206
|
-
collection: collection
|
|
206
|
+
collection: collection
|
|
207
207
|
}
|
|
208
208
|
return slot unless callable
|
|
209
209
|
|
|
@@ -254,7 +254,7 @@ module ViewComponent
|
|
|
254
254
|
end
|
|
255
255
|
|
|
256
256
|
def raise_if_slot_registered(slot_name)
|
|
257
|
-
if
|
|
257
|
+
if registered_slots.key?(slot_name)
|
|
258
258
|
# TODO remove? This breaks overriding slots when slots are inherited
|
|
259
259
|
raise ArgumentError.new(
|
|
260
260
|
"#{self} declares the #{slot_name} slot multiple times.\n\n" \
|
|
@@ -287,8 +287,6 @@ module ViewComponent
|
|
|
287
287
|
|
|
288
288
|
if slot[:collection]
|
|
289
289
|
[]
|
|
290
|
-
else
|
|
291
|
-
nil
|
|
292
290
|
end
|
|
293
291
|
end
|
|
294
292
|
|
|
@@ -305,7 +303,7 @@ module ViewComponent
|
|
|
305
303
|
# 2. Since we've to pass block content to components when calling
|
|
306
304
|
# `render`, evaluating the block here would require us to call
|
|
307
305
|
# `view_context.capture` twice, which is slower
|
|
308
|
-
slot.__vc_content_block = block if
|
|
306
|
+
slot.__vc_content_block = block if block
|
|
309
307
|
|
|
310
308
|
# If class
|
|
311
309
|
if slot_definition[:renderable]
|
|
@@ -321,7 +319,7 @@ module ViewComponent
|
|
|
321
319
|
# methods like `content_tag` as well as parent component state.
|
|
322
320
|
renderable_function = slot_definition[:renderable_function].bind(self)
|
|
323
321
|
renderable_value =
|
|
324
|
-
if
|
|
322
|
+
if block
|
|
325
323
|
renderable_function.call(*args) do |*rargs|
|
|
326
324
|
view_context.capture(*rargs, &block)
|
|
327
325
|
end
|
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "view_component/render_preview_helper"
|
|
4
|
+
require "view_component/capybara_simple_session"
|
|
4
5
|
|
|
5
6
|
module ViewComponent
|
|
6
7
|
module TestHelpers
|
|
7
8
|
begin
|
|
8
9
|
require "capybara/minitest"
|
|
10
|
+
|
|
9
11
|
include Capybara::Minitest::Assertions
|
|
10
12
|
|
|
13
|
+
CapybaraSimpleSession::DSL_METHODS.each do |method|
|
|
14
|
+
if RUBY_VERSION >= "2.7"
|
|
15
|
+
class_eval <<~METHOD, __FILE__, __LINE__ + 1
|
|
16
|
+
def #{method}(...)
|
|
17
|
+
page.method("#{method}").call(...)
|
|
18
|
+
end
|
|
19
|
+
METHOD
|
|
20
|
+
else
|
|
21
|
+
define_method method do |*args, &block|
|
|
22
|
+
page.send method, *args, &block
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.included(mod)
|
|
28
|
+
Capybara::Node::Simple.send(:define_method, :to_capybara_node) do
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
11
33
|
def page
|
|
12
|
-
|
|
34
|
+
@page ||= CapybaraSimpleSession.new(rendered_content)
|
|
13
35
|
end
|
|
14
36
|
|
|
15
37
|
def refute_component_rendered
|
|
@@ -32,6 +54,9 @@ module ViewComponent
|
|
|
32
54
|
# @private
|
|
33
55
|
attr_reader :rendered_content
|
|
34
56
|
|
|
57
|
+
# Returns the result of a render_inline call.
|
|
58
|
+
#
|
|
59
|
+
# @return [String]
|
|
35
60
|
def rendered_component
|
|
36
61
|
ViewComponent::Deprecation.warn(
|
|
37
62
|
"`rendered_component` is deprecated and will be removed in v3.0.0. " \
|
|
@@ -52,6 +77,7 @@ module ViewComponent
|
|
|
52
77
|
# @param component [ViewComponent::Base, ViewComponent::Collection] The instance of the component to be rendered.
|
|
53
78
|
# @return [Nokogiri::HTML]
|
|
54
79
|
def render_inline(component, **args, &block)
|
|
80
|
+
@page = nil
|
|
55
81
|
@rendered_content =
|
|
56
82
|
if Rails.version.to_f >= 6.1
|
|
57
83
|
controller.view_context.render(component, args, &block)
|
|
@@ -73,6 +99,7 @@ module ViewComponent
|
|
|
73
99
|
# assert_text("Hello, World!")
|
|
74
100
|
# ```
|
|
75
101
|
def render_in_view_context(&block)
|
|
102
|
+
@page = nil
|
|
76
103
|
@rendered_content = controller.view_context.instance_exec(&block)
|
|
77
104
|
Nokogiri::HTML.fragment(@rendered_content)
|
|
78
105
|
end
|
|
@@ -9,7 +9,7 @@ module ViewComponent
|
|
|
9
9
|
module Translatable
|
|
10
10
|
extend ActiveSupport::Concern
|
|
11
11
|
|
|
12
|
-
HTML_SAFE_TRANSLATION_KEY = /(?:_|\b)html\z
|
|
12
|
+
HTML_SAFE_TRANSLATION_KEY = /(?:_|\b)html\z/
|
|
13
13
|
|
|
14
14
|
included do
|
|
15
15
|
class_attribute :i18n_backend, instance_writer: false, instance_predicate: false
|
|
@@ -23,14 +23,13 @@ module ViewComponent
|
|
|
23
23
|
def build_i18n_backend
|
|
24
24
|
return if CompileCache.compiled? self
|
|
25
25
|
|
|
26
|
-
if (translation_files = _sidecar_files(%w[yml yaml])).any?
|
|
27
|
-
|
|
26
|
+
self.i18n_backend = if (translation_files = _sidecar_files(%w[yml yaml])).any?
|
|
27
|
+
# Returning nil cleans up if translations file has been removed since the last compilation
|
|
28
|
+
|
|
29
|
+
I18nBackend.new(
|
|
28
30
|
i18n_scope: i18n_scope,
|
|
29
|
-
load_paths: translation_files
|
|
31
|
+
load_paths: translation_files
|
|
30
32
|
)
|
|
31
|
-
else
|
|
32
|
-
# Cleanup if translations file has been removed since the last compilation
|
|
33
|
-
self.i18n_backend = nil
|
|
34
33
|
end
|
|
35
34
|
end
|
|
36
35
|
end
|
|
@@ -50,7 +49,7 @@ module ViewComponent
|
|
|
50
49
|
|
|
51
50
|
def scope_data(data)
|
|
52
51
|
@i18n_scope.reverse_each do |part|
|
|
53
|
-
data = {
|
|
52
|
+
data = {part => data}
|
|
54
53
|
end
|
|
55
54
|
data
|
|
56
55
|
end
|
|
@@ -95,7 +94,7 @@ module ViewComponent
|
|
|
95
94
|
super(key, locale: locale, **options)
|
|
96
95
|
end
|
|
97
96
|
end
|
|
98
|
-
|
|
97
|
+
alias_method :t, :translate
|
|
99
98
|
|
|
100
99
|
# Exposes .i18n_scope as an instance method
|
|
101
100
|
def i18n_scope
|
|
@@ -109,7 +108,7 @@ module ViewComponent
|
|
|
109
108
|
# It's assumed here that objects loaded by the i18n backend will respond to `#html_safe?`.
|
|
110
109
|
# It's reasonable that if we're in Rails, `active_support/core_ext/string/output_safety.rb`
|
|
111
110
|
# will provide this to `Object`.
|
|
112
|
-
translation.html_safe
|
|
111
|
+
translation.html_safe
|
|
113
112
|
end
|
|
114
113
|
end
|
|
115
114
|
|
data/lib/view_component.rb
CHANGED
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.
|
|
4
|
+
version: 2.59.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: 2022-
|
|
11
|
+
date: 2022-07-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -185,19 +185,19 @@ dependencies:
|
|
|
185
185
|
- !ruby/object:Gem::Version
|
|
186
186
|
version: '13.0'
|
|
187
187
|
- !ruby/object:Gem::Dependency
|
|
188
|
-
name:
|
|
188
|
+
name: standard
|
|
189
189
|
requirement: !ruby/object:Gem::Requirement
|
|
190
190
|
requirements:
|
|
191
191
|
- - "~>"
|
|
192
192
|
- !ruby/object:Gem::Version
|
|
193
|
-
version:
|
|
193
|
+
version: '1'
|
|
194
194
|
type: :development
|
|
195
195
|
prerelease: false
|
|
196
196
|
version_requirements: !ruby/object:Gem::Requirement
|
|
197
197
|
requirements:
|
|
198
198
|
- - "~>"
|
|
199
199
|
- !ruby/object:Gem::Version
|
|
200
|
-
version:
|
|
200
|
+
version: '1'
|
|
201
201
|
- !ruby/object:Gem::Dependency
|
|
202
202
|
name: simplecov
|
|
203
203
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -325,6 +325,7 @@ files:
|
|
|
325
325
|
- lib/rails/generators/test_unit/templates/component_test.rb.tt
|
|
326
326
|
- lib/view_component.rb
|
|
327
327
|
- lib/view_component/base.rb
|
|
328
|
+
- lib/view_component/capybara_simple_session.rb
|
|
328
329
|
- lib/view_component/collection.rb
|
|
329
330
|
- lib/view_component/compile_cache.rb
|
|
330
331
|
- lib/view_component/compiler.rb
|