view_component 3.23.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/concerns/view_component/preview_actions.rb +11 -14
- data/app/controllers/view_components_system_test_controller.rb +15 -20
- data/app/views/test_mailer/test_asset_email.html.erb +1 -0
- data/app/views/test_mailer/test_url_email.html.erb +1 -0
- data/app/views/view_components/preview.html.erb +1 -9
- data/docs/CHANGELOG.md +404 -0
- data/lib/{rails/generators → generators/view_component}/abstract_generator.rb +2 -2
- data/lib/{rails/generators → generators/view_component}/component/component_generator.rb +16 -3
- data/lib/{rails/generators → generators/view_component}/component/templates/component.rb.tt +6 -1
- data/lib/{rails/generators/erb/component_generator.rb → generators/view_component/erb/erb_generator.rb} +4 -3
- data/lib/{rails/generators/haml/component_generator.rb → generators/view_component/haml/haml_generator.rb} +3 -3
- data/lib/{rails/generators/locale/component_generator.rb → generators/view_component/locale/locale_generator.rb} +3 -3
- data/lib/{rails/generators/preview/component_generator.rb → generators/view_component/preview/preview_generator.rb} +3 -3
- data/lib/{rails/generators/rspec/component_generator.rb → generators/view_component/rspec/rspec_generator.rb} +3 -3
- data/lib/{rails/generators/slim/component_generator.rb → generators/view_component/slim/slim_generator.rb} +3 -3
- data/lib/{rails/generators/stimulus/component_generator.rb → generators/view_component/stimulus/stimulus_generator.rb} +3 -3
- data/lib/generators/view_component/tailwindcss/tailwindcss_generator.rb +11 -0
- data/lib/{rails/generators/test_unit/component_generator.rb → generators/view_component/test_unit/test_unit_generator.rb} +2 -2
- data/lib/view_component/base.rb +154 -157
- data/lib/view_component/collection.rb +11 -25
- data/lib/view_component/compiler.rb +52 -79
- data/lib/view_component/config.rb +51 -85
- data/lib/view_component/configurable.rb +1 -1
- data/lib/view_component/deprecation.rb +1 -1
- data/lib/view_component/engine.rb +37 -107
- data/lib/view_component/errors.rb +16 -34
- data/lib/view_component/inline_template.rb +3 -4
- data/lib/view_component/instrumentation.rb +4 -10
- data/lib/view_component/preview.rb +4 -11
- data/lib/view_component/request_details.rb +30 -0
- data/lib/view_component/slot.rb +6 -13
- data/lib/view_component/slotable.rb +82 -77
- data/lib/view_component/system_spec_helpers.rb +11 -0
- data/lib/view_component/system_test_helpers.rb +1 -2
- data/lib/view_component/template.rb +106 -83
- data/lib/view_component/test_helpers.rb +37 -44
- data/lib/view_component/translatable.rb +33 -32
- data/lib/view_component/version.rb +3 -3
- data/lib/view_component.rb +8 -6
- metadata +30 -558
- data/app/assets/vendor/prism.css +0 -4
- data/app/assets/vendor/prism.min.js +0 -12
- data/app/helpers/preview_helper.rb +0 -85
- data/app/views/view_components/_preview_source.html.erb +0 -17
- data/lib/rails/generators/tailwindcss/component_generator.rb +0 -11
- data/lib/view_component/capture_compatibility.rb +0 -44
- data/lib/view_component/component_error.rb +0 -6
- data/lib/view_component/rails/tasks/view_component.rake +0 -20
- data/lib/view_component/render_component_helper.rb +0 -10
- data/lib/view_component/render_component_to_string_helper.rb +0 -9
- data/lib/view_component/render_monkey_patch.rb +0 -13
- data/lib/view_component/render_to_string_monkey_patch.rb +0 -13
- data/lib/view_component/rendering_component_helper.rb +0 -9
- data/lib/view_component/rendering_monkey_patch.rb +0 -13
- data/lib/view_component/slotable_default.rb +0 -20
- data/lib/view_component/use_helpers.rb +0 -42
- /data/lib/{rails/generators → generators/view_component}/erb/templates/component.html.erb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/haml/templates/component.html.haml.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/preview/templates/component_preview.rb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/rspec/templates/component_spec.rb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/slim/templates/component.html.slim.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.js.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.ts.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/tailwindcss/templates/component.html.erb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/test_unit/templates/component_test.rb.tt +0 -0
@@ -18,18 +18,7 @@ module ViewComponent
|
|
18
18
|
def assert_component_rendered
|
19
19
|
assert_selector("body")
|
20
20
|
end
|
21
|
-
rescue LoadError
|
22
|
-
# We don't have a test case for running an application without capybara installed.
|
23
|
-
# It's probably fine to leave this without coverage.
|
24
|
-
# :nocov:
|
25
|
-
if ENV["DEBUG"]
|
26
|
-
warn(
|
27
|
-
"WARNING in `ViewComponent::TestHelpers`: Add `capybara` " \
|
28
|
-
"to Gemfile to use Capybara assertions."
|
29
|
-
)
|
30
|
-
end
|
31
|
-
|
32
|
-
# :nocov:
|
21
|
+
rescue LoadError # We don't have a test case for running an application without capybara installed.
|
33
22
|
end
|
34
23
|
|
35
24
|
# Returns the result of a render_inline call.
|
@@ -46,21 +35,12 @@ module ViewComponent
|
|
46
35
|
# ```
|
47
36
|
#
|
48
37
|
# @param component [ViewComponent::Base, ViewComponent::Collection] The instance of the component to be rendered.
|
49
|
-
# @return [Nokogiri::
|
38
|
+
# @return [Nokogiri::HTML5]
|
50
39
|
def render_inline(component, **args, &block)
|
51
40
|
@page = nil
|
52
|
-
@rendered_content =
|
53
|
-
if Rails.version.to_f >= 6.1
|
54
|
-
vc_test_controller.view_context.render(component, args, &block)
|
55
|
-
|
56
|
-
# :nocov:
|
57
|
-
else
|
58
|
-
vc_test_controller.view_context.render_component(component, &block)
|
59
|
-
end
|
41
|
+
@rendered_content = vc_test_controller.view_context.render(component, args, &block)
|
60
42
|
|
61
|
-
|
62
|
-
|
63
|
-
Nokogiri::HTML.fragment(@rendered_content)
|
43
|
+
Nokogiri::HTML5.fragment(@rendered_content)
|
64
44
|
end
|
65
45
|
|
66
46
|
# `JSON.parse`-d component output.
|
@@ -91,9 +71,9 @@ module ViewComponent
|
|
91
71
|
# @param name [String] The name of the preview to be rendered.
|
92
72
|
# @param from [ViewComponent::Preview] The class of the preview to be rendered.
|
93
73
|
# @param params [Hash] Parameters to be passed to the preview.
|
94
|
-
# @return [Nokogiri::
|
74
|
+
# @return [Nokogiri::HTML5]
|
95
75
|
def render_preview(name, from: __vc_test_helpers_preview_class, params: {})
|
96
|
-
previews_controller = __vc_test_helpers_build_controller(Rails.application.config.view_component.
|
76
|
+
previews_controller = __vc_test_helpers_build_controller(Rails.application.config.view_component.previews.controller.constantize)
|
97
77
|
|
98
78
|
# From what I can tell, it's not possible to overwrite all request parameters
|
99
79
|
# at once, so we set them individually here.
|
@@ -107,7 +87,7 @@ module ViewComponent
|
|
107
87
|
|
108
88
|
@rendered_content = result
|
109
89
|
|
110
|
-
Nokogiri::
|
90
|
+
Nokogiri::HTML5.fragment(@rendered_content)
|
111
91
|
end
|
112
92
|
|
113
93
|
# Execute the given block in the view context (using `instance_exec`).
|
@@ -121,12 +101,11 @@ module ViewComponent
|
|
121
101
|
#
|
122
102
|
# assert_text("Hello, World!")
|
123
103
|
# ```
|
124
|
-
def render_in_view_context(
|
104
|
+
def render_in_view_context(...)
|
125
105
|
@page = nil
|
126
|
-
@rendered_content = vc_test_controller.view_context.instance_exec(
|
127
|
-
Nokogiri::
|
106
|
+
@rendered_content = vc_test_controller.view_context.instance_exec(...)
|
107
|
+
Nokogiri::HTML5.fragment(@rendered_content)
|
128
108
|
end
|
129
|
-
ruby2_keywords(:render_in_view_context) if respond_to?(:ruby2_keywords, true)
|
130
109
|
|
131
110
|
# Set the Action Pack request variant for the given block:
|
132
111
|
#
|
@@ -136,11 +115,11 @@ module ViewComponent
|
|
136
115
|
# end
|
137
116
|
# ```
|
138
117
|
#
|
139
|
-
# @param
|
140
|
-
def with_variant(
|
118
|
+
# @param variants [Symbol[]] The variants to be set for the provided block.
|
119
|
+
def with_variant(*variants)
|
141
120
|
old_variants = vc_test_controller.view_context.lookup_context.variants
|
142
121
|
|
143
|
-
vc_test_controller.view_context.lookup_context.variants
|
122
|
+
vc_test_controller.view_context.lookup_context.variants += variants
|
144
123
|
yield
|
145
124
|
ensure
|
146
125
|
vc_test_controller.view_context.lookup_context.variants = old_variants
|
@@ -173,9 +152,14 @@ module ViewComponent
|
|
173
152
|
# end
|
174
153
|
# ```
|
175
154
|
#
|
176
|
-
# @param
|
177
|
-
def with_format(
|
178
|
-
|
155
|
+
# @param formats [Symbol[]] The format(s) to be set for the provided block.
|
156
|
+
def with_format(*formats)
|
157
|
+
old_formats = vc_test_controller.view_context.lookup_context.formats
|
158
|
+
|
159
|
+
vc_test_controller.view_context.lookup_context.formats = formats
|
160
|
+
yield
|
161
|
+
ensure
|
162
|
+
vc_test_controller.view_context.lookup_context.formats = old_formats
|
179
163
|
end
|
180
164
|
|
181
165
|
# Set the URL of the current request (such as when using request-dependent path helpers):
|
@@ -205,7 +189,7 @@ module ViewComponent
|
|
205
189
|
# @param full_path [String] The path to set for the current request.
|
206
190
|
# @param host [String] The host to set for the current request.
|
207
191
|
# @param method [String] The request method to set for the current request.
|
208
|
-
def with_request_url(full_path, host: nil, method: nil
|
192
|
+
def with_request_url(full_path, host: nil, method: nil)
|
209
193
|
old_request_host = vc_test_request.host
|
210
194
|
old_request_method = vc_test_request.request_method
|
211
195
|
old_request_path_info = vc_test_request.path_info
|
@@ -225,7 +209,6 @@ module ViewComponent
|
|
225
209
|
vc_test_request.set_header("action_dispatch.request.query_parameters",
|
226
210
|
Rack::Utils.parse_nested_query(query).with_indifferent_access)
|
227
211
|
vc_test_request.set_header(Rack::QUERY_STRING, query)
|
228
|
-
vc_test_request.format = format
|
229
212
|
yield
|
230
213
|
ensure
|
231
214
|
vc_test_request.host = old_request_host
|
@@ -250,7 +233,20 @@ module ViewComponent
|
|
250
233
|
#
|
251
234
|
# @return [ActionController::Base]
|
252
235
|
def vc_test_controller
|
253
|
-
@vc_test_controller ||= __vc_test_helpers_build_controller(
|
236
|
+
@vc_test_controller ||= __vc_test_helpers_build_controller(vc_test_controller_class)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Set the controller used by `render_inline`:
|
240
|
+
#
|
241
|
+
# ```ruby
|
242
|
+
# def vc_test_controller_class
|
243
|
+
# MyTestController
|
244
|
+
# end
|
245
|
+
# ```
|
246
|
+
def vc_test_controller_class
|
247
|
+
return @__vc_test_controller_class if defined?(@__vc_test_controller_class)
|
248
|
+
|
249
|
+
defined?(ApplicationController) ? ApplicationController : ActionController::Base
|
254
250
|
end
|
255
251
|
|
256
252
|
# Access the request used by `render_inline`:
|
@@ -284,11 +280,9 @@ module ViewComponent
|
|
284
280
|
|
285
281
|
def __vc_test_helpers_preview_class
|
286
282
|
result = if respond_to?(:described_class)
|
287
|
-
|
288
|
-
raise "`render_preview` expected a described_class, but it is nil." if described_class.nil?
|
283
|
+
raise ArgumentError.new("`render_preview` expected a described_class, but it is nil.") if described_class.nil?
|
289
284
|
|
290
285
|
"#{described_class}Preview"
|
291
|
-
# :nocov:
|
292
286
|
else
|
293
287
|
self.class.name.gsub("Test", "Preview")
|
294
288
|
end
|
@@ -296,6 +290,5 @@ module ViewComponent
|
|
296
290
|
rescue NameError
|
297
291
|
raise NameError, "`render_preview` expected to find #{result}, but it does not exist."
|
298
292
|
end
|
299
|
-
# :nocov:
|
300
293
|
end
|
301
294
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "erb"
|
4
|
-
require "set"
|
5
4
|
require "i18n"
|
6
5
|
require "active_support/concern"
|
7
6
|
|
@@ -10,19 +9,22 @@ module ViewComponent
|
|
10
9
|
extend ActiveSupport::Concern
|
11
10
|
|
12
11
|
HTML_SAFE_TRANSLATION_KEY = /(?:_|\b)html\z/
|
12
|
+
private_constant :HTML_SAFE_TRANSLATION_KEY
|
13
|
+
|
13
14
|
TRANSLATION_EXTENSIONS = %w[yml yaml].freeze
|
15
|
+
private_constant :TRANSLATION_EXTENSIONS
|
14
16
|
|
15
17
|
included do
|
16
|
-
class_attribute :
|
18
|
+
class_attribute :__vc_i18n_backend, instance_writer: false, instance_predicate: false
|
17
19
|
end
|
18
20
|
|
19
21
|
class_methods do
|
20
|
-
def
|
21
|
-
@
|
22
|
+
def __vc_i18n_scope
|
23
|
+
@__vc_i18n_scope ||= virtual_path.sub(%r{^/}, "").gsub(%r{/_?}, ".")
|
22
24
|
end
|
23
25
|
|
24
|
-
def
|
25
|
-
return if
|
26
|
+
def __vc_build_i18n_backend
|
27
|
+
return if __vc_compiled?
|
26
28
|
|
27
29
|
# We need to load the translations files from the ancestors so a component
|
28
30
|
# can inherit translations from its parent and is able to overwrite them.
|
@@ -33,31 +35,31 @@ module ViewComponent
|
|
33
35
|
end
|
34
36
|
|
35
37
|
# In development it will become nil if the translations file is removed
|
36
|
-
self.
|
38
|
+
self.__vc_i18n_backend = if translation_files.any?
|
37
39
|
I18nBackend.new(
|
38
|
-
|
40
|
+
scope: __vc_i18n_scope,
|
39
41
|
load_paths: translation_files
|
40
42
|
)
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
def
|
46
|
+
def __vc_i18n_key(key, scope = nil)
|
45
47
|
scope = scope.join(".") if scope.is_a? Array
|
46
48
|
key = key&.to_s unless key.is_a?(String)
|
47
49
|
key = "#{scope}.#{key}" if scope
|
48
|
-
key = "#{
|
50
|
+
key = "#{__vc_i18n_scope}#{key}" if key.start_with?(".")
|
49
51
|
key
|
50
52
|
end
|
51
53
|
|
52
54
|
def translate(key = nil, **options)
|
53
55
|
return key.map { |k| translate(k, **options) } if key.is_a?(Array)
|
54
56
|
|
55
|
-
|
57
|
+
__vc_ensure_compiled
|
56
58
|
|
57
59
|
locale = options.delete(:locale) || ::I18n.locale
|
58
|
-
key =
|
60
|
+
key = __vc_i18n_key(key, options.delete(:scope))
|
59
61
|
|
60
|
-
|
62
|
+
__vc_i18n_backend.translate(locale, key, options)
|
61
63
|
end
|
62
64
|
|
63
65
|
alias_method :t, :translate
|
@@ -66,18 +68,18 @@ module ViewComponent
|
|
66
68
|
class I18nBackend < ::I18n::Backend::Simple
|
67
69
|
EMPTY_HASH = {}.freeze
|
68
70
|
|
69
|
-
def initialize(
|
70
|
-
@
|
71
|
-
@
|
71
|
+
def initialize(scope:, load_paths:)
|
72
|
+
@__vc_i18n_scope = scope.split(".").map(&:to_sym)
|
73
|
+
@__vc_load_paths = load_paths
|
72
74
|
end
|
73
75
|
|
74
76
|
# Ensure the Simple backend won't load paths from ::I18n.load_path
|
75
77
|
def load_translations
|
76
|
-
super(@
|
78
|
+
super(@__vc_load_paths)
|
77
79
|
end
|
78
80
|
|
79
81
|
def scope_data(data)
|
80
|
-
@
|
82
|
+
@__vc_i18n_scope.reverse_each do |part|
|
81
83
|
data = {part => data}
|
82
84
|
end
|
83
85
|
data
|
@@ -91,44 +93,43 @@ module ViewComponent
|
|
91
93
|
def translate(key = nil, **options)
|
92
94
|
raise ViewComponent::TranslateCalledBeforeRenderError if view_context.nil?
|
93
95
|
|
94
|
-
return
|
96
|
+
return @view_context.translate(key, **options) unless __vc_i18n_backend
|
95
97
|
return key.map { |k| translate(k, **options) } if key.is_a?(Array)
|
96
98
|
|
97
99
|
locale = options.delete(:locale) || ::I18n.locale
|
98
|
-
key = self.class.
|
100
|
+
key = self.class.__vc_i18n_key(key, options.delete(:scope))
|
99
101
|
as_html = HTML_SAFE_TRANSLATION_KEY.match?(key)
|
100
102
|
|
101
|
-
|
103
|
+
__vc_html_escape_translation_options!(options) if as_html
|
102
104
|
|
103
|
-
if key.start_with?(
|
105
|
+
if key.start_with?(__vc_i18n_scope + ".")
|
104
106
|
translated =
|
105
107
|
catch(:exception) do
|
106
|
-
|
108
|
+
__vc_i18n_backend.translate(locale, key, options)
|
107
109
|
end
|
108
110
|
|
109
111
|
# Fallback to the global translations
|
110
112
|
if translated.is_a? ::I18n::MissingTranslation
|
111
|
-
return
|
113
|
+
return @view_context.translate(key, locale: locale, **options)
|
112
114
|
end
|
113
115
|
|
114
|
-
translated =
|
116
|
+
translated = __vc_html_safe_translation(translated) if as_html
|
115
117
|
translated
|
116
118
|
else
|
117
|
-
|
119
|
+
@view_context.translate(key, locale: locale, **options)
|
118
120
|
end
|
119
121
|
end
|
120
122
|
alias_method :t, :translate
|
121
123
|
|
122
|
-
|
123
|
-
|
124
|
-
self.class.i18n_scope
|
124
|
+
def __vc_i18n_scope
|
125
|
+
self.class.__vc_i18n_scope
|
125
126
|
end
|
126
127
|
|
127
128
|
private
|
128
129
|
|
129
|
-
def
|
130
|
+
def __vc_html_safe_translation(translation)
|
130
131
|
if translation.respond_to?(:map)
|
131
|
-
translation.map { |element|
|
132
|
+
translation.map { |element| __vc_html_safe_translation(element) }
|
132
133
|
else
|
133
134
|
# It's assumed here that objects loaded by the i18n backend will respond to `#html_safe?`.
|
134
135
|
# It's reasonable that if we're in Rails, `active_support/core_ext/string/output_safety.rb`
|
@@ -137,7 +138,7 @@ module ViewComponent
|
|
137
138
|
end
|
138
139
|
end
|
139
140
|
|
140
|
-
def
|
141
|
+
def __vc_html_escape_translation_options!(options)
|
141
142
|
options.except(*::I18n::RESERVED_KEYS).each do |name, value|
|
142
143
|
next if name == :count && value.is_a?(Numeric)
|
143
144
|
|
data/lib/view_component.rb
CHANGED
@@ -7,20 +7,22 @@ module ViewComponent
|
|
7
7
|
extend ActiveSupport::Autoload
|
8
8
|
|
9
9
|
autoload :Base
|
10
|
-
autoload :CaptureCompatibility
|
11
10
|
autoload :Compiler
|
12
11
|
autoload :CompileCache
|
13
|
-
autoload :ComponentError
|
14
12
|
autoload :Config
|
15
13
|
autoload :Deprecation
|
16
14
|
autoload :InlineTemplate
|
17
15
|
autoload :Instrumentation
|
18
16
|
autoload :Preview
|
19
|
-
autoload :TestHelpers
|
20
|
-
autoload :SystemTestHelpers
|
21
|
-
autoload :TestCase
|
22
|
-
autoload :SystemTestCase
|
23
17
|
autoload :Translatable
|
18
|
+
|
19
|
+
if Rails.env.test?
|
20
|
+
autoload :TestHelpers
|
21
|
+
autoload :SystemSpecHelpers
|
22
|
+
autoload :SystemTestHelpers
|
23
|
+
autoload :TestCase
|
24
|
+
autoload :SystemTestCase
|
25
|
+
end
|
24
26
|
end
|
25
27
|
|
26
28
|
require "view_component/engine" if defined?(Rails::Engine)
|