From 2b754d7876ad1ff0b60b20c511f9389fcf043d17 Mon Sep 17 00:00:00 2001 From: omarluq Date: Thu, 15 Feb 2024 00:29:48 -0600 Subject: [PATCH] add turbo stream morph action to broadcasts --- app/channels/turbo/streams/broadcasts.rb | 8 ++++ app/models/concerns/turbo/broadcastable.rb | 23 ++++++++++ app/models/turbo/streams/tag_builder.rb | 26 +++++++++++ test/streams/broadcastable_test.rb | 50 +++++++++++++++++++++- test/streams/streams_channel_test.rb | 30 +++++++++++++ 5 files changed, 135 insertions(+), 2 deletions(-) diff --git a/app/channels/turbo/streams/broadcasts.rb b/app/channels/turbo/streams/broadcasts.rb index de1c0330..8f216fc5 100644 --- a/app/channels/turbo/streams/broadcasts.rb +++ b/app/channels/turbo/streams/broadcasts.rb @@ -95,6 +95,14 @@ def refresh_debouncer_for(*streamables, request_id: nil) # :nodoc: Turbo::ThreadDebouncer.for("turbo-refresh-debouncer-#{stream_name_from(streamables.including(request_id))}") end + def broadcast_morph_to(*streamables, **opts) + broadcast_action_to(*streamables, action: :morph, **opts) + end + + def broadcast_morph_later_to(*streamables, **opts) + broadcast_action_later_to(*streamables, action: :morph, **opts) + end + private def render_format(format, **rendering) ApplicationController.render(formats: [ format ], **rendering) diff --git a/app/models/concerns/turbo/broadcastable.rb b/app/models/concerns/turbo/broadcastable.rb index 29970dcd..30e328b3 100644 --- a/app/models/concerns/turbo/broadcastable.rb +++ b/app/models/concerns/turbo/broadcastable.rb @@ -491,6 +491,29 @@ def broadcast_render_later_to(*streamables, **rendering) Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts? end + # Broadcast a morph action to the stream name identified by the passed streamables. Example: + # sends + # to the stream named "identity:2:clearances" + # clearance.broadcast_morph_to examiner.identity, :clearances + def broadcast_morph_to(*streamables, **rendering) + Turbo::StreamsChannel.broadcast_morph_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts? + end + + # Same as broadcast_morph_to but the designated stream is automatically set to the current model. + def broadcast_morph(**rendering) + broadcast_morph_to(self, target: self, **rendering) + end + + # Same as broadcast_morph_to but run asynchronously via a Turbo::Streams::BroadcastJob. + def broadcast_morph_later_to(*streamables, **rendering) + Turbo::StreamsChannel.broadcast_morph_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts? + end + + # Same as broadcast_morph_later_to but the designated stream is automatically set to the current model. + def broadcast_morph_later(target: broadcast_target_default, **rendering) + broadcast_morph_later_to self, **rendering + end + private def broadcast_target_default self.class.broadcast_target_default diff --git a/app/models/turbo/streams/tag_builder.rb b/app/models/turbo/streams/tag_builder.rb index 99a2aa47..294c4e76 100644 --- a/app/models/turbo/streams/tag_builder.rb +++ b/app/models/turbo/streams/tag_builder.rb @@ -228,6 +228,32 @@ def prepend_all(targets, content = nil, **rendering, &block) action_all :prepend, targets, content, **rendering, &block end + # Morph the target in the dom with either the content passed in or a rendering result determined + # by the rendering keyword arguments, the content in the block, or the rendering of the target as a record. Examples: + # + # <%= turbo_stream.morph "clearance_5", "
Morph the dom target identified by clearance_5
" %> + # <%= turbo_stream.morph clearance %> + # <%= turbo_stream.morph clearance, partial: "clearances/clearance", locals: { title: "Hello" } %> + # <%= turbo_stream.morph "clearance_5" do %> + #
Morph the dom target identified by clearance_5
+ # <% end %> + def morph(target, content = nil, **rendering, &block) + action :morph, target, content, **rendering, &block + end + + # Morph the targets in the dom with either the content passed in or a rendering result determined + # by the rendering keyword arguments, the content in the block, or the rendering of the targets as a record. Examples: + # + # <%= turbo_stream.morph_all ".clearance_item", "
Morph the dom target identified by the class clearance_item
" %> + # <%= turbo_stream.morph_all clearance %> + # <%= turbo_stream.morph_all clearance, partial: "clearances/clearance", locals: { title: "Hello" } %> + # <%= turbo_stream.morph_all ".clearance_item" do %> + #
Morph the dom target identified by the class clearance_item
+ # <% end %> + def morph_all(targets, content = nil, **rendering, &block) + action_all :morph, targets, content, **rendering, &block + end + # Send an action of the type name to target. Options described in the concrete methods. def action(name, target, content = nil, allow_inferred_rendering: true, **rendering, &block) template = render_template(target, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block) diff --git a/test/streams/broadcastable_test.rb b/test/streams/broadcastable_test.rb index 6460405a..5c086289 100644 --- a/test/streams/broadcastable_test.rb +++ b/test/streams/broadcastable_test.rb @@ -224,6 +224,30 @@ class Turbo::BroadcastableTest < ActionCable::Channel::TestCase @message.broadcast_render_to @profile end end + + test "broadcasting morph to stream now" do + assert_broadcast_on "stream", turbo_stream_action_tag("morph", target: "message_1", template: render(@message)) do + @message.broadcast_morph_to "stream", target: "message_1" + end + end + + test "broadcasting morph to stream now targeting children-only children-only" do + assert_broadcast_on "stream", turbo_stream_action_tag("morph", target: "message_1", 'children-only': true, template: render(@message)) do + @message.broadcast_morph_to "stream", target: "message_1", attributes: { 'children-only': true } + end + end + + test "broadcasting morph now" do + assert_broadcast_on @message.to_gid_param, turbo_stream_action_tag("morph", target: "message_1", template: render(@message)) do + @message.broadcast_morph target: "message_1" + end + end + + test "broadcasting morph now targeting children-only" do + assert_broadcast_on @message.to_gid_param, turbo_stream_action_tag("morph", target: "message_1", 'children-only': true, template: render(@message)) do + @message.broadcast_morph target: "message_1", attributes: { 'children-only': true } + end + end end class Turbo::BroadcastableArticleTest < ActionCable::Channel::TestCase @@ -518,6 +542,30 @@ class Turbo::SuppressingBroadcastsTest < ActionCable::Channel::TestCase end end + test "suppressing broadcasting morph to stream now" do + assert_no_broadcasts_when_suppressing do + @message.broadcast_morph_to "stream" + end + end + + test "suppressing broadcasting morph to stream later" do + assert_no_broadcasts_later_when_supressing do + @message.broadcast_morph_later_to "stream" + end + end + + test "suppressing broadcasting morph now" do + assert_no_broadcasts_when_suppressing do + @message.broadcast_morph + end + end + + test "suppressing broadcasting morph later" do + assert_no_broadcasts_later_when_supressing do + @message.broadcast_morph_later + end + end + private def assert_no_broadcasts_when_suppressing assert_no_broadcasts @message.to_gid_param do @@ -535,5 +583,3 @@ def assert_no_broadcasts_later_when_supressing end end end - - diff --git a/test/streams/streams_channel_test.rb b/test/streams/streams_channel_test.rb index 5b015edb..433092a1 100644 --- a/test/streams/streams_channel_test.rb +++ b/test/streams/streams_channel_test.rb @@ -255,4 +255,34 @@ class Turbo::StreamsChannelTest < ActionCable::Channel::TestCase Turbo::StreamsChannel.broadcast_stream_to "stream", content: "direct" end end + + test "broadcasting morph now" do + options = { partial: "messages/message", locals: { message: "hello!" } } + + assert_broadcast_on "stream", turbo_stream_action_tag("morph", target: "message_1", template: render(options)) do + Turbo::StreamsChannel.broadcast_morph_to "stream", target: "message_1", **options + end + + assert_broadcast_on "stream", turbo_stream_action_tag("morph", targets: ".message", template: render(options)) do + Turbo::StreamsChannel.broadcast_morph_to "stream", targets: ".message", **options + end + end + + test "broadcasting morph later" do + options = { partial: "messages/message", locals: { message: "hello!" } } + + assert_broadcast_on "stream", turbo_stream_action_tag("morph", target: "message_1", template: render(options)) do + perform_enqueued_jobs do + Turbo::StreamsChannel.broadcast_morph_later_to \ + "stream", target: "message_1", **options + end + end + + assert_broadcast_on "stream", turbo_stream_action_tag("morph", targets: ".message", template: render(options)) do + perform_enqueued_jobs do + Turbo::StreamsChannel.broadcast_morph_later_to \ + "stream", targets: ".message", **options + end + end + end end