Skip to content

Commit cc7c313

Browse files
authored
Add output_preamble to match postamble (ViewComponent#1960)
1 parent 5a55ad5 commit cc7c313

12 files changed

+94
-5
lines changed

.DS_Store

6 KB
Binary file not shown.

docs/CHANGELOG.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ nav_order: 5
1010

1111
## main
1212

13+
* Add `output_preamble` to match `output_postamble`, using the same safety checks.
14+
15+
*Kali Donovan*
16+
*Michael Daross*
17+
1318
* Exclude html escaping of I18n reserved keys with `I18n::RESERVED_KEYS` rather than `I18n.reserved_keys_pattern`.
1419

1520
*Nick Coyne*
@@ -30,7 +35,7 @@ nav_order: 5
3035

3136
* Add support for Ruby 3.3.
3237

33-
*Reegan Viljoen*
38+
*Reegan Viljoen*
3439

3540
* Allow translations to be inherited and overridden in subclasses.
3641

docs/api.md

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ that inhibits encapsulation & reuse, often making testing difficult.
7878
A proxy through which to access helpers. Use sparingly as doing so introduces
7979
coupling that inhibits encapsulation & reuse, often making testing difficult.
8080

81+
### `#output_preamble`[String]
82+
83+
Optional content to be returned before the rendered template.
84+
8185
### `#output_postamble`[String]
8286

8387
Optional content to be returned after the rendered template.

docs/index.md

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ ViewComponent is built by over a hundred members of the community, including:
124124
<img src="https://avatars.githubusercontent.com/cesariouy?s=64" alt="cesariouy" width="32" />
125125
<img src="https://avatars.githubusercontent.com/cover?s=64" alt="cover" width="32" />
126126
<img src="https://avatars.githubusercontent.com/cpjmcquillan?s=64" alt="cpjmcquillan" width="32" />
127+
<img src="https://avatars.githubusercontent.com/crookedgrin?s=64" alt="crookedgrin" width="32" />
127128
<img src="https://avatars.githubusercontent.com/czj?s=64" alt="czj" width="32" />
128129
<img src="https://avatars.githubusercontent.com/dani-sc?s=64" alt="dani-sc" width="32" />
129130
<img src="https://avatars.githubusercontent.com/danieldiekmeier?s=64" alt="danieldiekmeier" width="32" />
@@ -158,6 +159,7 @@ ViewComponent is built by over a hundred members of the community, including:
158159
<img src="https://avatars.githubusercontent.com/jwshuff?s=64" alt="jwshuff" width="32" />
159160
<img src="https://avatars.githubusercontent.com/kaspermeyer?s=64" alt="kaspermeyer" width="32" />
160161
<img src="https://avatars.githubusercontent.com/kylefox?s=64" alt="kylefox" width="32" />
162+
<img src="https://avatars.githubusercontent.com/kdonovan?s=64" alt="kdonovan" width="32" />
161163
<img src="https://avatars.githubusercontent.com/leighhalliday?s=64" alt="leighhalliday" width="32" />
162164
<img src="https://avatars.githubusercontent.com/llenk?s=64" alt="llenk" width="32" />
163165
<img src="https://avatars.githubusercontent.com/manuelpuyol?s=64" alt="manuelpuyol" width="32" />

lib/view_component/base.rb

+19-4
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,13 @@ def render_in(view_context, &block)
104104
before_render
105105

106106
if render?
107-
# Avoid allocating new string when output_postamble is blank
108-
if output_postamble.blank?
109-
safe_render_template_for(@__vc_variant).to_s
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
110112
else
111-
safe_render_template_for(@__vc_variant).to_s + safe_output_postamble
113+
safe_output_preamble + rendered_template + safe_output_postamble
112114
end
113115
else
114116
""
@@ -156,6 +158,13 @@ def render_parent_to_string
156158
end
157159
end
158160

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+
159168
# Optional content to be returned after the rendered template.
160169
#
161170
# @return [String]
@@ -329,6 +338,12 @@ def safe_render_template_for(variant)
329338
end
330339
end
331340

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+
332347
def safe_output_postamble
333348
maybe_escape_html(output_postamble) do
334349
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.")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
class BeforeAndAfterRenderComponent < ViewComponent::Base
4+
def call
5+
"Hello, ".html_safe
6+
end
7+
8+
def output_preamble
9+
"Well, ".html_safe
10+
end
11+
12+
def output_postamble
13+
"World!".html_safe
14+
end
15+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
class BeforeRenderComponent < ViewComponent::Base
4+
def call
5+
"Hello!".html_safe
6+
end
7+
8+
def output_preamble
9+
"Well, ".html_safe
10+
end
11+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
class UnsafePreambleComponent < ViewComponent::Base
4+
def call
5+
"<div>some content</div>".html_safe
6+
end
7+
8+
def output_preamble
9+
"<script>alert('hello!')</script>"
10+
end
11+
end

test/sandbox/app/controllers/integration_examples_controller.rb

+4
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ def unsafe_component
6464
render(UnsafeComponent.new)
6565
end
6666

67+
def unsafe_preamble_component
68+
render(UnsafePreambleComponent.new)
69+
end
70+
6771
def unsafe_postamble_component
6872
render(UnsafePostambleComponent.new)
6973
end

test/sandbox/config/routes.rb

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
get :inherited_sidecar, to: "integration_examples#inherited_sidecar"
3131
get :inherited_from_uncompilable_component, to: "integration_examples#inherited_from_uncompilable_component"
3232
get :unsafe_component, to: "integration_examples#unsafe_component"
33+
get :unsafe_preamble_component, to: "integration_examples#unsafe_preamble_component"
3334
get :unsafe_postamble_component, to: "integration_examples#unsafe_postamble_component"
3435
post :create, to: "integration_examples#create"
3536

test/sandbox/test/integration_test.rb

+9
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,15 @@ def test_unsafe_component
733733
)
734734
end
735735

736+
def test_unsafe_preamble_component
737+
warnings = capture_warnings { get "/unsafe_preamble_component" }
738+
assert_select("script", false)
739+
assert(
740+
warnings.any? { |warning| warning.include?("component was provided an HTML-unsafe preamble") },
741+
"Rendering UnsafePreambleComponent did not emit an HTML safety warning"
742+
)
743+
end
744+
736745
def test_unsafe_postamble_component
737746
warnings = capture_warnings { get "/unsafe_postamble_component" }
738747
assert_select("script", false)

test/sandbox/test/rendering_test.rb

+12
Original file line numberDiff line numberDiff line change
@@ -889,12 +889,24 @@ def test_components_share_helpers_state
889889
assert_equal 1, PartialHelper::State.calls
890890
end
891891

892+
def test_output_preamble
893+
render_inline(BeforeRenderComponent.new)
894+
895+
assert_text("Well, Hello!")
896+
end
897+
892898
def test_output_postamble
893899
render_inline(AfterRenderComponent.new)
894900

895901
assert_text("Hello, World!")
896902
end
897903

904+
def test_output_preamble_and_postamble
905+
render_inline(BeforeAndAfterRenderComponent.new)
906+
907+
assert_text("Well, Hello, World!")
908+
end
909+
898910
def test_compilation_in_development_mode
899911
with_compiler_mode(ViewComponent::Compiler::DEVELOPMENT_MODE) do
900912
with_new_cache do

0 commit comments

Comments
 (0)