view_component 3.8.0 → 3.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1bb442fbd129d82aadace3ec12fcf80c62bb9fbd9f0f204ac6668aec801e904b
4
- data.tar.gz: d39cbee68cace857998a380a80bcd7cd0b5c0b6d22774d36a82e2072d2a8631f
3
+ metadata.gz: d3f4f53b04ea58ed971bef33e1bea8ab65c2564c64a49927ca5db90c118d23bc
4
+ data.tar.gz: 1a6b6024ffd5baf72cb80726b0853cf3fcab80d43505c87648cc57d6726e9185
5
5
  SHA512:
6
- metadata.gz: 966508a6e21eae5af04e14fabdfd43fbc77dd85f39bc3332b6519686a5da1eb404a8f23e4b25a90cafeaa9143a81437f6a4ca0cf0c57690132bd384030b66071
7
- data.tar.gz: 4bc638d9dc1f925a6aa2c75e6f2f0e0f10ed01e61e39eac37c6a2f496f4eff66f50022ec8f7850c94761a40c3a064365f1b02957c35aaa15b3c17fc0f796b328
6
+ metadata.gz: 0f75de114591fca8e662e6fa15086c0b6b11c5862a05e68abff123ba19b7b23b7f67e7b65f8adca8a78688011254117643e9f0e76b92e5a0c0c51d82ecb85194
7
+ data.tar.gz: 9771fc3c30b8472bb2f0793573926cd7b05e8f991091af18f8e51cfd5370ea8ac7f1eff40488eb0e1434a81683e901fcd74659763620f2e5ae8a26d4db0c943b
data/docs/CHANGELOG.md CHANGED
@@ -10,6 +10,58 @@ nav_order: 5
10
10
 
11
11
  ## main
12
12
 
13
+ ## 3.10.0
14
+
15
+ * Fix html escaping in `#call` for non-strings.
16
+
17
+ *Reegan Viljoen, Cameron Dutro*
18
+
19
+ * Add `output_preamble` to match `output_postamble`, using the same safety checks.
20
+
21
+ *Kali Donovan, Michael Daross*
22
+
23
+ * Exclude html escaping of I18n reserved keys with `I18n::RESERVED_KEYS` rather than `I18n.reserved_keys_pattern`.
24
+
25
+ *Nick Coyne*
26
+
27
+ * Update CI configuration to use `Appraisal`.
28
+
29
+ *Hans Lemuet, Simon Fish*
30
+
31
+ ## 3.9.0
32
+
33
+ * Don’t break `rails stats` if ViewComponent path is missing.
34
+
35
+ *Claudio Baccigalupo*
36
+
37
+ * Add deprecation warnings for EOL ruby and Rails versions and patches associated with them.
38
+
39
+ *Reegan Viljoen*
40
+
41
+ * Add support for Ruby 3.3.
42
+
43
+ *Reegan Viljoen*
44
+
45
+ * Allow translations to be inherited and overridden in subclasses.
46
+
47
+ *Elia Schito*
48
+
49
+ * Resolve console warnings when running test suite.
50
+
51
+ *Joel Hawksley*
52
+
53
+ * Fix spelling in a local variable.
54
+
55
+ *Olle Jonsson*
56
+
57
+ * Avoid duplicating rendered string when `output_postamble` is blank.
58
+
59
+ *Mitchell Henke*
60
+
61
+ * Ensure HTML output safety.
62
+
63
+ *Cameron Dutro*
64
+
13
65
  ## 3.8.0
14
66
 
15
67
  * Use correct value for the `config.action_dispatch.show_exceptions` config option for edge Rails.
@@ -225,6 +277,17 @@ This release makes the following breaking changes, many of which have long been
225
277
 
226
278
  *Joel Hawksley*
227
279
 
280
+ For example:
281
+
282
+ ```diff
283
+ <%= render BlogComponent.new do |component| %>
284
+ - <% component.header do %>
285
+ + <% component.with_header do %>
286
+ <%= link_to "My blog", root_path %>
287
+ <% end %>
288
+ <% end %>
289
+ ```
290
+
228
291
  * BREAKING: Remove deprecated SlotsV1 in favor of current SlotsV2.
229
292
 
230
293
  *Joel Hawksley*
@@ -34,11 +34,11 @@ module Locale
34
34
  end
35
35
 
36
36
  def destination(locale = nil)
37
- extention = ".#{locale}" if locale
37
+ extension = ".#{locale}" if locale
38
38
  if sidecar?
39
- File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component#{extention}.yml")
39
+ File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component#{extension}.yml")
40
40
  else
41
- File.join(component_path, class_path, "#{file_name}_component#{extention}.yml")
41
+ File.join(component_path, class_path, "#{file_name}_component#{extension}.yml")
42
42
  end
43
43
  end
44
44
  end
@@ -104,7 +104,14 @@ module ViewComponent
104
104
  before_render
105
105
 
106
106
  if render?
107
- render_template_for(@__vc_variant).to_s + output_postamble
107
+ # Avoid allocating new string when output_preamble and output_postamble are blank
108
+ rendered_template = safe_render_template_for(@__vc_variant).to_s
109
+
110
+ if output_preamble.blank? && output_postamble.blank?
111
+ rendered_template
112
+ else
113
+ safe_output_preamble + rendered_template + safe_output_postamble
114
+ end
108
115
  else
109
116
  ""
110
117
  end
@@ -151,11 +158,18 @@ module ViewComponent
151
158
  end
152
159
  end
153
160
 
161
+ # Optional content to be returned before the rendered template.
162
+ #
163
+ # @return [String]
164
+ def output_preamble
165
+ @@default_output_preamble ||= "".html_safe
166
+ end
167
+
154
168
  # Optional content to be returned after the rendered template.
155
169
  #
156
170
  # @return [String]
157
171
  def output_postamble
158
- ""
172
+ @@default_output_postamble ||= "".html_safe
159
173
  end
160
174
 
161
175
  # Called before rendering the component. Override to perform operations that
@@ -221,6 +235,7 @@ module ViewComponent
221
235
  end
222
236
 
223
237
  if ::Rails.env.development? || ::Rails.env.test?
238
+ # @private
224
239
  def method_missing(method_name, *args) # rubocop:disable Style/MissingRespondToMissing
225
240
  super
226
241
  rescue => e # rubocop:disable Style/RescueStandardError
@@ -301,6 +316,44 @@ module ViewComponent
301
316
  defined?(@__vc_content_evaluated) && @__vc_content_evaluated
302
317
  end
303
318
 
319
+ def maybe_escape_html(text)
320
+ return text if request && !request.format.html?
321
+ return text if text.blank?
322
+
323
+ if text.html_safe?
324
+ text
325
+ else
326
+ yield
327
+ html_escape(text)
328
+ end
329
+ end
330
+
331
+ def safe_render_template_for(variant)
332
+ if compiler.renders_template_for_variant?(variant)
333
+ render_template_for(variant)
334
+ else
335
+ maybe_escape_html(render_template_for(variant)) do
336
+ Kernel.warn("WARNING: The #{self.class} component rendered HTML-unsafe output. The output will be automatically escaped, but you may want to investigate.")
337
+ end
338
+ end
339
+ end
340
+
341
+ def safe_output_preamble
342
+ maybe_escape_html(output_preamble) do
343
+ Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe preamble. The preamble will be automatically escaped, but you may want to investigate.")
344
+ end
345
+ end
346
+
347
+ def safe_output_postamble
348
+ maybe_escape_html(output_postamble) do
349
+ Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe postamble. The postamble will be automatically escaped, but you may want to investigate.")
350
+ end
351
+ end
352
+
353
+ def compiler
354
+ @compiler ||= self.class.compiler
355
+ end
356
+
304
357
  # Set the controller used for testing components:
305
358
  #
306
359
  # ```ruby
@@ -616,7 +669,7 @@ module ViewComponent
616
669
 
617
670
  # @private
618
671
  def collection_counter_parameter
619
- "#{collection_parameter}_counter".to_sym
672
+ :"#{collection_parameter}_counter"
620
673
  end
621
674
 
622
675
  # @private
@@ -626,7 +679,7 @@ module ViewComponent
626
679
 
627
680
  # @private
628
681
  def collection_iteration_parameter
629
- "#{collection_parameter}_iteration".to_sym
682
+ :"#{collection_parameter}_iteration"
630
683
  end
631
684
 
632
685
  # @private
@@ -16,6 +16,7 @@ module ViewComponent
16
16
  def initialize(component_class)
17
17
  @component_class = component_class
18
18
  @redefinition_lock = Mutex.new
19
+ @variants_rendering_templates = Set.new
19
20
  end
20
21
 
21
22
  def compiled?
@@ -56,7 +57,7 @@ module ViewComponent
56
57
  RUBY
57
58
  # rubocop:enable Style/EvalWithLocation
58
59
 
59
- component_class.define_method("_call_#{safe_class_name}", component_class.instance_method(:call))
60
+ component_class.define_method(:"_call_#{safe_class_name}", component_class.instance_method(:call))
60
61
 
61
62
  component_class.silence_redefinition_of_method("render_template_for")
62
63
  component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -68,6 +69,7 @@ module ViewComponent
68
69
  else
69
70
  templates.each do |template|
70
71
  method_name = call_method_name(template[:variant])
72
+ @variants_rendering_templates << template[:variant]
71
73
 
72
74
  redefinition_lock.synchronize do
73
75
  component_class.silence_redefinition_of_method(method_name)
@@ -89,6 +91,10 @@ module ViewComponent
89
91
  CompileCache.register(component_class)
90
92
  end
91
93
 
94
+ def renders_template_for_variant?(variant)
95
+ @variants_rendering_templates.include?(variant)
96
+ end
97
+
92
98
  private
93
99
 
94
100
  attr_reader :component_class, :redefinition_lock
@@ -101,7 +107,7 @@ module ViewComponent
101
107
  "elsif variant.to_sym == :'#{variant}'\n #{safe_name}"
102
108
  end.join("\n")
103
109
 
104
- component_class.define_method("_call_#{safe_class_name}", component_class.instance_method(:call))
110
+ component_class.define_method(:"_call_#{safe_class_name}", component_class.instance_method(:call))
105
111
 
106
112
  body = <<-RUBY
107
113
  if variant.nil?
@@ -81,6 +81,8 @@ module ViewComponent
81
81
  next if Rails.version.to_f >= 6.1 || !app.config.view_component.render_monkey_patch_enabled
82
82
 
83
83
  # :nocov:
84
+ ViewComponent::Deprecation.deprecation_warning("Monkey patching `render`", "ViewComponent 4.0 will remove the `render` monkey patch")
85
+
84
86
  ActiveSupport.on_load(:action_view) do
85
87
  require "view_component/render_monkey_patch"
86
88
  ActionView::Base.prepend ViewComponent::RenderMonkeyPatch
@@ -99,6 +101,8 @@ module ViewComponent
99
101
  next if Rails.version.to_f >= 6.1
100
102
 
101
103
  # :nocov:
104
+ ViewComponent::Deprecation.deprecation_warning("using `render_component`", "ViewComponent 4.0 will remove `render_component`")
105
+
102
106
  ActiveSupport.on_load(:action_view) do
103
107
  require "view_component/render_component_helper"
104
108
  ActionView::Base.include ViewComponent::RenderComponentHelper
@@ -160,6 +164,16 @@ module ViewComponent
160
164
  end
161
165
  end
162
166
 
167
+ # :nocov:
168
+ if RUBY_VERSION < "3.0.0"
169
+ ViewComponent::Deprecation.deprecation_warning("Support for Ruby versions < 3.0.0", "ViewComponent 4.0 will remove support for Ruby versions < 3.0.0 ")
170
+ end
171
+
172
+ if Rails.version.to_f < 6.1
173
+ ViewComponent::Deprecation.deprecation_warning("Support for Rails versions < 6.1", "ViewComponent 4.0 will remove support for Rails versions < 6.1 ")
174
+ end
175
+ # :nocov:
176
+
163
177
  app.executor.to_run :before do
164
178
  CompileCache.invalidate! unless ActionView::Base.cache_template_loading
165
179
  end
@@ -4,7 +4,11 @@ require "active_support/descendants_tracker"
4
4
 
5
5
  module ViewComponent # :nodoc:
6
6
  class Preview
7
- include Rails.application.routes.url_helpers if defined?(Rails.application.routes.url_helpers)
7
+ if defined?(Rails.application.routes.url_helpers)
8
+ # Workaround from https://stackoverflow.com/questions/20853526/make-yard-ignore-certain-class-extensions to appease YARD
9
+ send(:include, Rails.application.routes.url_helpers)
10
+ end
11
+
8
12
  include ActionView::Helpers::TagHelper
9
13
  include ActionView::Helpers::AssetTagHelper
10
14
  extend ActiveSupport::DescendantsTracker
@@ -7,7 +7,8 @@ namespace :view_component do
7
7
  # :nocov:
8
8
  require "rails/code_statistics"
9
9
 
10
- ::STATS_DIRECTORIES << ["ViewComponents", ViewComponent::Base.view_component_path]
10
+ dir = ViewComponent::Base.view_component_path
11
+ ::STATS_DIRECTORIES << ["ViewComponents", dir] if File.directory?(Rails.root + dir)
11
12
  # :nocov:
12
13
  end
13
14
  end
@@ -93,11 +93,11 @@ module ViewComponent
93
93
  get_slot(slot_name)
94
94
  end
95
95
 
96
- define_method "#{slot_name}?" do
96
+ define_method :"#{slot_name}?" do
97
97
  get_slot(slot_name).present?
98
98
  end
99
99
 
100
- define_method "with_#{slot_name}_content" do |content|
100
+ define_method :"with_#{slot_name}_content" do |content|
101
101
  send(setter_method_name) { content.to_s }
102
102
 
103
103
  self
@@ -160,7 +160,7 @@ module ViewComponent
160
160
  end
161
161
  ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
162
162
 
163
- define_method "with_#{singular_name}_content" do |content|
163
+ define_method :"with_#{singular_name}_content" do |content|
164
164
  send(setter_method_name) { content.to_s }
165
165
 
166
166
  self
@@ -180,7 +180,7 @@ module ViewComponent
180
180
  get_slot(slot_name)
181
181
  end
182
182
 
183
- define_method "#{slot_name}?" do
183
+ define_method :"#{slot_name}?" do
184
184
  get_slot(slot_name).present?
185
185
  end
186
186
 
@@ -211,7 +211,7 @@ module ViewComponent
211
211
  get_slot(slot_name)
212
212
  end
213
213
 
214
- define_method("#{slot_name}?") do
214
+ define_method(:"#{slot_name}?") do
215
215
  get_slot(slot_name).present?
216
216
  end
217
217
 
@@ -246,7 +246,7 @@ module ViewComponent
246
246
  end
247
247
  ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
248
248
 
249
- define_method "with_#{poly_slot_name}_content" do |content|
249
+ define_method :"with_#{poly_slot_name}_content" do |content|
250
250
  send(setter_method_name) { content.to_s }
251
251
 
252
252
  self
@@ -175,16 +175,17 @@ module ViewComponent
175
175
  # end
176
176
  # ```
177
177
  #
178
- # @param path [String] The path to set for the current request.
178
+ # @param full_path [String] The path to set for the current request.
179
179
  # @param host [String] The host to set for the current request.
180
180
  # @param method [String] The request method to set for the current request.
181
- def with_request_url(full_path, host: nil, method: nil)
181
+ def with_request_url(full_path, host: nil, method: nil, format: :html)
182
182
  old_request_host = vc_test_request.host
183
183
  old_request_method = vc_test_request.request_method
184
184
  old_request_path_info = vc_test_request.path_info
185
185
  old_request_path_parameters = vc_test_request.path_parameters
186
186
  old_request_query_parameters = vc_test_request.query_parameters
187
187
  old_request_query_string = vc_test_request.query_string
188
+ old_request_format = vc_test_request.format.symbol
188
189
  old_controller = defined?(@vc_test_controller) && @vc_test_controller
189
190
 
190
191
  path, query = full_path.split("?", 2)
@@ -197,6 +198,7 @@ module ViewComponent
197
198
  vc_test_request.set_header("action_dispatch.request.query_parameters",
198
199
  Rack::Utils.parse_nested_query(query).with_indifferent_access)
199
200
  vc_test_request.set_header(Rack::QUERY_STRING, query)
201
+ vc_test_request.format = format
200
202
  yield
201
203
  ensure
202
204
  vc_test_request.host = old_request_host
@@ -205,6 +207,7 @@ module ViewComponent
205
207
  vc_test_request.path_parameters = old_request_path_parameters
206
208
  vc_test_request.set_header("action_dispatch.request.query_parameters", old_request_query_parameters)
207
209
  vc_test_request.set_header(Rack::QUERY_STRING, old_request_query_string)
210
+ vc_test_request.format = old_request_format
208
211
  @vc_test_controller = old_controller
209
212
  end
210
213
 
@@ -252,9 +255,11 @@ module ViewComponent
252
255
 
253
256
  def __vc_test_helpers_preview_class
254
257
  result = if respond_to?(:described_class)
258
+ # :nocov:
255
259
  raise "`render_preview` expected a described_class, but it is nil." if described_class.nil?
256
260
 
257
261
  "#{described_class}Preview"
262
+ # :nocov:
258
263
  else
259
264
  self.class.name.gsub("Test", "Preview")
260
265
  end
@@ -262,5 +267,6 @@ module ViewComponent
262
267
  rescue NameError
263
268
  raise NameError, "`render_preview` expected to find #{result}, but it does not exist."
264
269
  end
270
+ # :nocov:
265
271
  end
266
272
  end
@@ -10,6 +10,7 @@ module ViewComponent
10
10
  extend ActiveSupport::Concern
11
11
 
12
12
  HTML_SAFE_TRANSLATION_KEY = /(?:_|\b)html\z/
13
+ TRANSLATION_EXTENSIONS = %w[yml yaml].freeze
13
14
 
14
15
  included do
15
16
  class_attribute :i18n_backend, instance_writer: false, instance_predicate: false
@@ -23,9 +24,16 @@ module ViewComponent
23
24
  def build_i18n_backend
24
25
  return if compiled?
25
26
 
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
27
+ # We need to load the translations files from the ancestors so a component
28
+ # can inherit translations from its parent and is able to overwrite them.
29
+ translation_files = ancestors.reverse_each.with_object([]) do |ancestor, files|
30
+ if ancestor.is_a?(Class) && ancestor < ViewComponent::Base
31
+ files.concat(ancestor.sidecar_files(TRANSLATION_EXTENSIONS))
32
+ end
33
+ end
28
34
 
35
+ # In development it will become nil if the translations file is removed
36
+ self.i18n_backend = if translation_files.any?
29
37
  I18nBackend.new(
30
38
  i18n_scope: i18n_scope,
31
39
  load_paths: translation_files
@@ -130,8 +138,7 @@ module ViewComponent
130
138
  end
131
139
 
132
140
  def html_escape_translation_options!(options)
133
- options.each do |name, value|
134
- next if ::I18n.reserved_keys_pattern.match?(name)
141
+ options.except(*::I18n::RESERVED_KEYS).each do |name, value|
135
142
  next if name == :count && value.is_a?(Numeric)
136
143
 
137
144
  options[name] = ERB::Util.html_escape(value.to_s)
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 3
6
- MINOR = 8
6
+ MINOR = 10
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
 
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: 3.8.0
4
+ version: 3.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ViewComponent Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-27 00:00:00.000000000 Z
11
+ date: 2024-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -78,14 +78,14 @@ dependencies:
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: 2.12.0
81
+ version: 2.13.0
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: 2.12.0
88
+ version: 2.13.0
89
89
  - !ruby/object:Gem::Dependency
90
90
  name: better_html
91
91
  requirement: !ruby/object:Gem::Requirement
@@ -302,14 +302,14 @@ dependencies:
302
302
  requirements:
303
303
  - - "~>"
304
304
  - !ruby/object:Gem::Version
305
- version: 0.9.25
305
+ version: 0.9.34
306
306
  type: :development
307
307
  prerelease: false
308
308
  version_requirements: !ruby/object:Gem::Requirement
309
309
  requirements:
310
310
  - - "~>"
311
311
  - !ruby/object:Gem::Version
312
- version: 0.9.25
312
+ version: 0.9.34
313
313
  - !ruby/object:Gem::Dependency
314
314
  name: yard-activesupport-concern
315
315
  requirement: !ruby/object:Gem::Requirement
@@ -421,7 +421,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
421
421
  - !ruby/object:Gem::Version
422
422
  version: '0'
423
423
  requirements: []
424
- rubygems_version: 3.4.5
424
+ rubygems_version: 3.5.3
425
425
  signing_key:
426
426
  specification_version: 4
427
427
  summary: A framework for building reusable, testable & encapsulated view components