Skip to content

Commit

Permalink
Add tests for rendering parents from inline templates (i.e. #call met…
Browse files Browse the repository at this point in the history
…hods); add helpful #render_parent_to_string method
  • Loading branch information
camertron committed Jul 24, 2023
1 parent 5f2309e commit 11fdbcf
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 4 deletions.
13 changes: 13 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ double render if they emit the result.
parent template considering the current variant and emits the result without
double-rendering.

### `#render_parent_to_string`

Renders the parent component to a string and returns it. This method is meant
to be used inside custom #call methods when a string result is desired, eg.

```ruby
def call
"<div>#{render_parent_to_string}</div>"
end
```

When rendering the parent inside an .erb template, use `#render_parent` instead.

### `#request`[ActionDispatch::Request]

The current request. Use sparingly as doing so introduces coupling that
Expand Down
20 changes: 16 additions & 4 deletions docs/guide/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,31 @@ class MyComponent < ViewComponent::Base
end
```

To render a parent component's template from a `#call` method, call `super`.
Finally, `#render_parent` also works inside `#call` methods:

```ruby
class MyComponent < ViewComponent::Base
def call
content_tag("div") do
render_parent
end
end
end
```

When composing `#call` methods, keep in mind that `#render_parent` does not return a string. If a string is desired, call `#render_parent_to_string` instead. For example:

Check failure on line 158 in docs/guide/templates.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/guide/templates.md#L158

[Microsoft.Contractions] Use 'doesn't' instead of 'does not'.
Raw output
{"message": "[Microsoft.Contractions] Use 'doesn't' instead of 'does not'.", "location": {"path": "docs/guide/templates.md", "range": {"start": {"line": 158, "column": 68}}}, "severity": "ERROR"}

```ruby
class MyComponent < ViewComponent::Base
# "phone" variant
def call_phone
"<div>#{super}</div>"
content_tag("div") do
"<div>#{render_parent_to_string}</div>"
end
end
end
```

`super` will attempt to call the `#call_phone` method on the parent class. If the parent class doesn't support the "phone" variant, Ruby will raise a `NoMethodError`. Consider using a template and `#render_parent` to handle superclass variants automatically.

## Trailing whitespace

Code editors commonly add a trailing newline character to source files in keeping with the Unix standard. Including trailing whitespace in component templates can result in unwanted whitespace in the HTML, eg. if the component is rendered before the period at the end of a sentence.
Expand Down
14 changes: 14 additions & 0 deletions lib/view_component/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ def render_parent
end
end

# Renders the parent component to a string and returns it. This method is meant
# to be used inside custom #call methods when a string result is desired, eg.
#
# ```ruby
# def call
# "<div>#{render_parent_to_string}</div>"
# end
# ```
#
# When rendering the parent inside an .erb template, use `#render_parent` instead.
def render_parent_to_string
capture { render_parent }
end

# Optional content to be returned after the rendered template.
#
# @return [String]
Expand Down
7 changes: 7 additions & 0 deletions test/sandbox/app/components/inline_level1_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class InlineLevel1Component < ViewComponent::Base
def call
content_tag(:div, class: "level1-component")
end
end
11 changes: 11 additions & 0 deletions test/sandbox/app/components/inline_level2_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class InlineLevel2Component < Level2Component
def call
"<div level2-component base>#{render_parent_to_string}</div>"
end

def call_variant
"<div level2-component variant>#{render_parent_to_string}</div>"
end
end
15 changes: 15 additions & 0 deletions test/sandbox/app/components/inline_level3_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

class InlineLevel3Component < Level2Component
def call
content_tag(:div, class: "level3-component base") do
render_parent
end
end

def call_variant
content_tag(:div, class: "level3-component variant") do
render_parent
end
end
end
22 changes: 22 additions & 0 deletions test/sandbox/test/rendering_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,28 @@ def test_child_components_fall_back_to_default_variant
assert_selector ".level3-component.base .level2-component.base .level1-component"
end

def test_child_components_can_render_parent_with_inline_templates
render_inline(InlineLevel3Component.new)

assert_selector(".level3-component.base .level2-component.base .level1-component")
end

def test_variant_propagates_to_parent_with_inline_templates
with_variant :variant do
render_inline(InlineLevel3Component.new)
end

assert_selector ".level3-component.variant .level2-component.variant .level1-component"
end

def test_child_components_fall_back_to_default_variant_with_inline_templates
with_variant :non_existent_variant do
render_inline(InlineLevel3Component.new)
end

assert_selector ".level3-component.base .level2-component.base .level1-component"
end

def test_component_renders_without_trailing_whitespace
template = File.read(Rails.root.join("app/components/trailing_whitespace_component.html.erb"))
assert template =~ /\s+\z/, "Template does not contain any trailing whitespace"
Expand Down

0 comments on commit 11fdbcf

Please sign in to comment.