From 1684d35e08a0980545071ca9c5a4aec453c27f59 Mon Sep 17 00:00:00 2001
From: David Alejandro
<15317732+davidalejandroaguilar@users.noreply.github.com>
Date: Thu, 19 Oct 2023 06:59:15 -0700
Subject: [PATCH] Allow broadcasting actions without rendering, and passing
stream tag attributes (#427)
In order to allow custom Turbo actions that don't require a template and work only via attributes passed to the turbo_stream_tag.
---
app/channels/turbo/streams/broadcasts.rb | 9 +-
.../turbo/streams/action_broadcast_job.rb | 4 +-
app/models/concerns/turbo/broadcastable.rb | 18 ++--
test/streams/broadcastable_test.rb | 64 ++++++++++++++
test/system/broadcasts_test.rb | 87 ++++++++++++++++++-
5 files changed, 164 insertions(+), 18 deletions(-)
diff --git a/app/channels/turbo/streams/broadcasts.rb b/app/channels/turbo/streams/broadcasts.rb
index f3aabd39..278e0fbf 100644
--- a/app/channels/turbo/streams/broadcasts.rb
+++ b/app/channels/turbo/streams/broadcasts.rb
@@ -33,9 +33,10 @@ def broadcast_prepend_to(*streamables, **opts)
broadcast_action_to(*streamables, action: :prepend, **opts)
end
- def broadcast_action_to(*streamables, action:, target: nil, targets: nil, **rendering)
+ def broadcast_action_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
broadcast_stream_to(*streamables, content: turbo_stream_action_tag(action, target: target, targets: targets, template:
- rendering.delete(:content) || rendering.delete(:html) || (rendering.any? ? render_format(:html, **rendering) : nil)
+ rendering.delete(:content) || rendering.delete(:html) || (rendering[:render] != false && rendering.any? ? render_format(:html, **rendering) : nil),
+ **attributes
))
end
@@ -63,9 +64,9 @@ def broadcast_prepend_later_to(*streamables, **opts)
broadcast_action_later_to(*streamables, action: :prepend, **opts)
end
- def broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, **rendering)
+ def broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
Turbo::Streams::ActionBroadcastJob.perform_later \
- stream_name_from(streamables), action: action, target: target, targets: targets, **rendering
+ stream_name_from(streamables), action: action, target: target, targets: targets, attributes: attributes, **rendering
end
def broadcast_render_to(*streamables, **rendering)
diff --git a/app/jobs/turbo/streams/action_broadcast_job.rb b/app/jobs/turbo/streams/action_broadcast_job.rb
index a47d7bd7..ae734b67 100644
--- a/app/jobs/turbo/streams/action_broadcast_job.rb
+++ b/app/jobs/turbo/streams/action_broadcast_job.rb
@@ -2,7 +2,7 @@
class Turbo::Streams::ActionBroadcastJob < ActiveJob::Base
discard_on ActiveJob::DeserializationError
- def perform(stream, action:, target:, **rendering)
- Turbo::StreamsChannel.broadcast_action_to stream, action: action, target: target, **rendering
+ def perform(stream, action:, target:, attributes: {}, **rendering)
+ Turbo::StreamsChannel.broadcast_action_to stream, action: action, target: target, attributes: attributes, **rendering
end
end
diff --git a/app/models/concerns/turbo/broadcastable.rb b/app/models/concerns/turbo/broadcastable.rb
index d6c7fe04..2af2eeea 100644
--- a/app/models/concerns/turbo/broadcastable.rb
+++ b/app/models/concerns/turbo/broadcastable.rb
@@ -249,13 +249,13 @@ def broadcast_prepend(target: broadcast_target_default, **rendering)
# # Sends
Ryan
\n") do diff --git a/test/system/broadcasts_test.rb b/test/system/broadcasts_test.rb index 7babcbbc..2f324003 100644 --- a/test/system/broadcasts_test.rb +++ b/test/system/broadcasts_test.rb @@ -1,6 +1,8 @@ require "application_system_test_case" class BroadcastsTest < ApplicationSystemTestCase + include ActiveJob::TestHelper + test "Message broadcasts Turbo Streams" do visit messages_path wait_for_stream_to_be_connected @@ -22,21 +24,82 @@ class BroadcastsTest < ApplicationSystemTestCase test "Message broadcasts with renderable: render option" do visit messages_path wait_for_stream_to_be_connected - + assert_broadcasts_text "Test message", to: :messages do |text, target| Message.create(content: "Ignored").broadcast_append_to(target, renderable: MessageComponent.new(text)) end end - + test "Does not render the layout twice when passed a component" do visit messages_path wait_for_stream_to_be_connected - + Message.create(content: "Ignored").broadcast_append_to(:messages, renderable: MessageComponent.new("test")) - + assert_selector("title", count: 1, visible: false, text: "Dummy") end + test "Message broadcasts with extra attributes to turbo stream tag" do + visit messages_path + wait_for_stream_to_be_connected + + assert_broadcasts_text "Message 1", to: :messages do |text, target| + Message.create(content: text).broadcast_action_to(target, action: :append, attributes: { "data-foo": "bar" }) + end + end + + test "Message broadcasts with correct extra attributes to turbo stream tag" do + visit messages_path + wait_for_stream_to_be_connected + + assert_forwards_turbo_stream_tag_attribute attr_key: "data-foo", attr_value: "bar", to: :messages do |attr_key, attr_value, target| + Message.create(content: text).broadcast_action_to(target, action: :test, attributes: { attr_key => attr_value }) + end + end + + test "Message broadcasts with no rendering" do + visit messages_path + wait_for_stream_to_be_connected + + assert_forwards_turbo_stream_tag_attribute attr_key: "data-foo", attr_value: "bar", to: :messages do |attr_key, attr_value, target| + Message.create(content: text).broadcast_action_to(target, action: :test, render: false, partial: "non_existant", attributes: { attr_key => attr_value }) + end + end + + test "Message broadcasts later with extra attributes to turbo stream tag" do + visit messages_path + wait_for_stream_to_be_connected + + perform_enqueued_jobs do + assert_broadcasts_text "Message 1", to: :messages do |text, target| + Message.create(content: text).broadcast_action_later_to(target, action: :append, attributes: { "data-foo": "bar" }) + end + end + end + + + test "Message broadcasts later with correct extra attributes to turbo stream tag" do + visit messages_path + wait_for_stream_to_be_connected + + perform_enqueued_jobs do + assert_forwards_turbo_stream_tag_attribute attr_key: "data-foo", attr_value: "bar", to: :messages do |attr_key, attr_value, target| + Message.create(content: text).broadcast_action_later_to(target, action: :test, attributes: { attr_key => attr_value }) + end + end + end + + test "Message broadcasts later with no rendering" do + visit messages_path + wait_for_stream_to_be_connected + + perform_enqueued_jobs do + assert_forwards_turbo_stream_tag_attribute attr_key: "data-foo", attr_value: "bar", to: :messages do |attr_key, attr_value, target| + Message.create(content: text).broadcast_action_to(target, action: :test, render: false, partial: "non_existant", attributes: { attr_key => attr_value }) + end + end + end + test "Users::Profile broadcasts Turbo Streams" do visit users_profiles_path wait_for_stream_to_be_connected @@ -68,4 +131,20 @@ def assert_broadcasts_text(text, to:, &block) within(:element, id: to) { assert_text text } end + + def assert_forwards_turbo_stream_tag_attribute(attr_key:, attr_value:, to:, &block) + execute_script(<<~SCRIPT) + Turbo.StreamActions.test = function () { + const attribute = this.getAttribute('#{attr_key}') + + document.getElementById('#{to}').innerHTML = attribute + } + SCRIPT + + within(:element, id: to) { assert_no_text attr_value } + + [attr_key, attr_value, to].yield_self(&block) + + within(:element, id: to) { assert_text attr_value } + end end