From 3e4d34f66dffd3ce853e79f78457aa7687af2736 Mon Sep 17 00:00:00 2001 From: Blake Williams Date: Mon, 15 Mar 2021 11:36:24 -0400 Subject: [PATCH 1/3] Ensure blocks passed to slots are captured Currently blocks given to a lambda slot aren't always captured which can cause them to be rendered incorrectly in certain rendering situations. This fix resolves the issue by ensuring the block passed to the lambda slot is always captured. --- lib/view_component/slotable_v2.rb | 2 +- .../slots_v2_block_component.html.erb | 22 +++++++++++++++++++ .../components/slots_v2_block_component.rb | 5 +++++ test/view_component/slotable_v2_test.rb | 7 ++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 test/app/components/slots_v2_block_component.html.erb create mode 100644 test/app/components/slots_v2_block_component.rb diff --git a/lib/view_component/slotable_v2.rb b/lib/view_component/slotable_v2.rb index 9dcdff50d..4011968e8 100644 --- a/lib/view_component/slotable_v2.rb +++ b/lib/view_component/slotable_v2.rb @@ -226,7 +226,7 @@ def set_slot(slot_name, *args, **kwargs, &block) # Use `bind(self)` to ensure lambda is executed in the context of the # current component. This is necessary to allow the lambda to access helper # methods like `content_tag` as well as parent component state. - renderable_value = slot_definition[:renderable_function].bind(self).call(*args, **kwargs, &block) + renderable_value = slot_definition[:renderable_function].bind(self).call(*args, **kwargs) { view_context.capture(&block) } # Function calls can return components, so if it's a component handle it specially if renderable_value.respond_to?(:render_in) diff --git a/test/app/components/slots_v2_block_component.html.erb b/test/app/components/slots_v2_block_component.html.erb new file mode 100644 index 000000000..7154bfbc7 --- /dev/null +++ b/test/app/components/slots_v2_block_component.html.erb @@ -0,0 +1,22 @@ +<%= render SlotsV2Component.new(classes: "mt-4") do |component| %> + <%= component.title do %> + This is my title! + <% end %> + + <%= component.subtitle do %> + This is my subtitle! + <% end %> + + <%= component.tab do %> + Tab A + <% end %> + + <%= component.tab do %> + Tab B + <% end %> + + <%= component.footer(classes: "text-blue") do %> +

Footer part 1

+

Footer part 2

+ <% end %> +<% end %> diff --git a/test/app/components/slots_v2_block_component.rb b/test/app/components/slots_v2_block_component.rb new file mode 100644 index 000000000..df53cc372 --- /dev/null +++ b/test/app/components/slots_v2_block_component.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class SlotsV2BlockComponent < ViewComponent::Base + include ViewComponent::SlotableV2 +end diff --git a/test/view_component/slotable_v2_test.rb b/test/view_component/slotable_v2_test.rb index d7ecadb39..463977cb6 100644 --- a/test/view_component/slotable_v2_test.rb +++ b/test/view_component/slotable_v2_test.rb @@ -249,4 +249,11 @@ def test_slots_without_render_block assert_selector("h1", text: "This is my title!") end + + def test_slot_with_block_content + render_inline(SlotsV2BlockComponent.new) + + assert_selector("p", text: "Footer part 1") + assert_selector("p", text: "Footer part 2") + end end From 229c0d5d7add421c18d32332e104f9edb424a837 Mon Sep 17 00:00:00 2001 From: Blake Williams Date: Mon, 15 Mar 2021 15:07:37 -0400 Subject: [PATCH 2/3] Add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f56786d..849feb785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ *Joel Hawksley* +* Fix bug where blocks passed to lambda slots will render incorrectly in certain situations. + + *Blake Williams* + ## 2.27.0 * Allow customization of the controller used in component tests. From a1a89d87f79e4faa8b4194d712aaa0ae434023c9 Mon Sep 17 00:00:00 2001 From: Blake Williams Date: Mon, 15 Mar 2021 15:16:40 -0400 Subject: [PATCH 3/3] Don't raise if slot is not passed a block --- lib/view_component/slotable_v2.rb | 6 +++++- test/view_component/slotable_v2_test.rb | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/view_component/slotable_v2.rb b/lib/view_component/slotable_v2.rb index 4011968e8..e69772ab1 100644 --- a/lib/view_component/slotable_v2.rb +++ b/lib/view_component/slotable_v2.rb @@ -226,7 +226,11 @@ def set_slot(slot_name, *args, **kwargs, &block) # Use `bind(self)` to ensure lambda is executed in the context of the # current component. This is necessary to allow the lambda to access helper # methods like `content_tag` as well as parent component state. - renderable_value = slot_definition[:renderable_function].bind(self).call(*args, **kwargs) { view_context.capture(&block) } + renderable_value = if block_given? + slot_definition[:renderable_function].bind(self).call(*args, **kwargs) { view_context.capture(&block) } + else + slot_definition[:renderable_function].bind(self).call(*args, **kwargs) + end # Function calls can return components, so if it's a component handle it specially if renderable_value.respond_to?(:render_in) diff --git a/test/view_component/slotable_v2_test.rb b/test/view_component/slotable_v2_test.rb index 463977cb6..b4d17ef6a 100644 --- a/test/view_component/slotable_v2_test.rb +++ b/test/view_component/slotable_v2_test.rb @@ -256,4 +256,10 @@ def test_slot_with_block_content assert_selector("p", text: "Footer part 1") assert_selector("p", text: "Footer part 2") end + + def test_lambda_slot_with_missing_block + render_inline(SlotsV2Component.new(classes: "mt-4")) do |component| + component.footer(classes: "text-blue") + end + end end