Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add output_preamble to match postamble #1960

Merged
merged 6 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
7 changes: 6 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ nav_order: 5

## main

* Add `output_preamble` to match `output_postamble`, using the same safety checks.

*Kali Donovan*
*Michael Daross*

* Exclude html escaping of I18n reserved keys with `I18n::RESERVED_KEYS` rather than `I18n.reserved_keys_pattern`.

*Nick Coyne*
Expand All @@ -30,7 +35,7 @@ nav_order: 5

* Add support for Ruby 3.3.

*Reegan Viljoen*
*Reegan Viljoen*

* Allow translations to be inherited and overridden in subclasses.

Expand Down
4 changes: 4 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ that inhibits encapsulation & reuse, often making testing difficult.
A proxy through which to access helpers. Use sparingly as doing so introduces
coupling that inhibits encapsulation & reuse, often making testing difficult.

### `#output_preamble` → [String]

Optional content to be returned before the rendered template.

### `#output_postamble` → [String]

Optional content to be returned after the rendered template.
Expand Down
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ ViewComponent is built by over a hundred members of the community, including:
<img src="https://avatars.githubusercontent.com/cesariouy?s=64" alt="cesariouy" width="32" />
<img src="https://avatars.githubusercontent.com/cover?s=64" alt="cover" width="32" />
<img src="https://avatars.githubusercontent.com/cpjmcquillan?s=64" alt="cpjmcquillan" width="32" />
<img src="https://avatars.githubusercontent.com/crookedgrin?s=64" alt="crookedgrin" width="32" />
<img src="https://avatars.githubusercontent.com/czj?s=64" alt="czj" width="32" />
<img src="https://avatars.githubusercontent.com/dani-sc?s=64" alt="dani-sc" width="32" />
<img src="https://avatars.githubusercontent.com/danieldiekmeier?s=64" alt="danieldiekmeier" width="32" />
Expand Down Expand Up @@ -158,6 +159,7 @@ ViewComponent is built by over a hundred members of the community, including:
<img src="https://avatars.githubusercontent.com/jwshuff?s=64" alt="jwshuff" width="32" />
<img src="https://avatars.githubusercontent.com/kaspermeyer?s=64" alt="kaspermeyer" width="32" />
<img src="https://avatars.githubusercontent.com/kylefox?s=64" alt="kylefox" width="32" />
<img src="https://avatars.githubusercontent.com/kdonovan?s=64" alt="kdonovan" width="32" />
<img src="https://avatars.githubusercontent.com/leighhalliday?s=64" alt="leighhalliday" width="32" />
<img src="https://avatars.githubusercontent.com/llenk?s=64" alt="llenk" width="32" />
<img src="https://avatars.githubusercontent.com/manuelpuyol?s=64" alt="manuelpuyol" width="32" />
Expand Down
26 changes: 22 additions & 4 deletions lib/view_component/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,16 @@ def render_in(view_context, &block)
before_render

if render?
# Avoid allocating new string when output_postamble is blank
if output_postamble.blank?
safe_render_template_for(@__vc_variant).to_s
# Avoid allocating new string when output_preamble and output_postamble are blank
rendered_template = safe_render_template_for(@__vc_variant).to_s

if output_preamble.blank? && output_postamble.blank?
rendered_template
else
safe_render_template_for(@__vc_variant).to_s + safe_output_postamble
preamble = output_preamble.blank? ? "" : safe_output_preamble
postamble = output_postamble.blank? ? "" : safe_output_postamble

preamble + rendered_template + postamble
CrookedGrin marked this conversation as resolved.
Show resolved Hide resolved
end
else
""
Expand Down Expand Up @@ -156,6 +161,13 @@ def render_parent_to_string
end
end

# Optional content to be returned before the rendered template.
#
# @return [String]
def output_preamble
@@default_output_preamble ||= "".html_safe
end

# Optional content to be returned after the rendered template.
#
# @return [String]
Expand Down Expand Up @@ -329,6 +341,12 @@ def safe_render_template_for(variant)
end
end

def safe_output_preamble
maybe_escape_html(output_preamble) do
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.")
end
end

def safe_output_postamble
maybe_escape_html(output_postamble) do
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.")
Expand Down
15 changes: 15 additions & 0 deletions test/sandbox/app/components/before_and_after_render_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

class BeforeAndAfterRenderComponent < ViewComponent::Base
def call
"Hello, ".html_safe
end

def output_preamble
"Well, ".html_safe
end

def output_postamble
"World!".html_safe
end
end
11 changes: 11 additions & 0 deletions test/sandbox/app/components/before_render_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class BeforeRenderComponent < ViewComponent::Base
def call
"Hello!".html_safe
end

def output_preamble
"Well, ".html_safe
end
end
11 changes: 11 additions & 0 deletions test/sandbox/app/components/unsafe_preamble_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class UnsafePreambleComponent < ViewComponent::Base
def call
"<div>some content</div>".html_safe
end

def output_preamble
"<script>alert('hello!')</script>"
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ def unsafe_component
render(UnsafeComponent.new)
end

def unsafe_preamble_component
render(UnsafePreambleComponent.new)
end

def unsafe_postamble_component
render(UnsafePostambleComponent.new)
end
Expand Down
1 change: 1 addition & 0 deletions test/sandbox/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
get :inherited_sidecar, to: "integration_examples#inherited_sidecar"
get :inherited_from_uncompilable_component, to: "integration_examples#inherited_from_uncompilable_component"
get :unsafe_component, to: "integration_examples#unsafe_component"
get :unsafe_preamble_component, to: "integration_examples#unsafe_preamble_component"
get :unsafe_postamble_component, to: "integration_examples#unsafe_postamble_component"
post :create, to: "integration_examples#create"

Expand Down
9 changes: 9 additions & 0 deletions test/sandbox/test/integration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,15 @@ def test_unsafe_component
)
end

def test_unsafe_preamble_component
warnings = capture_warnings { get "/unsafe_preamble_component" }
assert_select("script", false)
assert(
warnings.any? { |warning| warning.include?("component was provided an HTML-unsafe preamble") },
"Rendering UnsafePreambleComponent did not emit an HTML safety warning"
)
end

def test_unsafe_postamble_component
warnings = capture_warnings { get "/unsafe_postamble_component" }
assert_select("script", false)
Expand Down
12 changes: 12 additions & 0 deletions test/sandbox/test/rendering_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -889,12 +889,24 @@ def test_components_share_helpers_state
assert_equal 1, PartialHelper::State.calls
end

def test_output_preamble
render_inline(BeforeRenderComponent.new)

assert_text("Well, Hello!")
end

def test_output_postamble
render_inline(AfterRenderComponent.new)

assert_text("Hello, World!")
end

def test_output_preamble_and_postamble
render_inline(BeforeAndAfterRenderComponent.new)

assert_text("Well, Hello, World!")
end

def test_compilation_in_development_mode
with_compiler_mode(ViewComponent::Compiler::DEVELOPMENT_MODE) do
with_new_cache do
Expand Down
Loading