diff --git a/app/models/turbo/streams/tag_builder.rb b/app/models/turbo/streams/tag_builder.rb
index 5b05f57c..93cf2e76 100644
--- a/app/models/turbo/streams/tag_builder.rb
+++ b/app/models/turbo/streams/tag_builder.rb
@@ -261,6 +261,8 @@ def action_all(name, targets, content = nil, method: nil, allow_inferred_renderi
private
def render_template(target, content = nil, allow_inferred_rendering: true, **rendering, &block)
case
+ when target.respond_to?(:render_in) && content.nil?
+ target.render_in(@view_context, &block)
when content.respond_to?(:render_in)
content.render_in(@view_context, &block)
when content
diff --git a/test/streams/streams_helper_test.rb b/test/streams/streams_helper_test.rb
index bf6ed47e..be9bea9f 100644
--- a/test/streams/streams_helper_test.rb
+++ b/test/streams/streams_helper_test.rb
@@ -3,8 +3,32 @@
class TestChannel < ApplicationCable::Channel; end
class Turbo::StreamsHelperTest < ActionView::TestCase
+ class Component
+ extend ActiveModel::Naming
+
+ def initialize(id:, content:) = (@id, @content = id, content)
+ def render_in(...) = @content
+ def to_key = [@id]
+ end
+
attr_accessor :formats
+ test "supports valid :renderable option object with nil content" do
+ component = Component.new(id: 1, content: "Hello, world")
+
+ assert_dom_equal <<~HTML.strip, turbo_stream.update(component)
+ Hello, world
+ HTML
+ end
+
+ test "supports valid :renderable option object with content" do
+ component = Component.new(id: 1, content: "Hello, world")
+
+ assert_dom_equal <<~HTML.strip, turbo_stream.update(component, "Raw content")
+ Raw content
+ HTML
+ end
+
test "with streamable" do
assert_dom_equal \
%(),
@@ -75,4 +99,12 @@ class Turbo::StreamsHelperTest < ActionView::TestCase
HTML
end
+
+ test "supports valid :partial option objects" do
+ message = Message.new(id: 1, content: "Hello, world")
+
+ assert_dom_equal <<~HTML.strip, turbo_stream.update(message)
+ Hello, world
+ HTML
+ end
end