diff --git a/app/channels/turbo/streams/broadcasts.rb b/app/channels/turbo/streams/broadcasts.rb
index f3aabd39..407b1573 100644
--- a/app/channels/turbo/streams/broadcasts.rb
+++ b/app/channels/turbo/streams/broadcasts.rb
@@ -33,6 +33,10 @@ def broadcast_prepend_to(*streamables, **opts)
broadcast_action_to(*streamables, action: :prepend, **opts)
end
+ def broadcast_refresh_to(*streamables, **opts)
+ broadcast_stream_to(*streamables, content: turbo_stream_refresh_tag)
+ end
+
def broadcast_action_to(*streamables, action:, target: nil, targets: nil, **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)
@@ -63,6 +67,10 @@ def broadcast_prepend_later_to(*streamables, **opts)
broadcast_action_later_to(*streamables, action: :prepend, **opts)
end
+ def broadcast_refresh_later_to(*streamables, **opts)
+ Turbo::Streams::BroadcastStreamJob.perform_later stream_name_from(streamables), content: turbo_stream_refresh_tag(**opts)
+ end
+
def broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, **rendering)
Turbo::Streams::ActionBroadcastJob.perform_later \
stream_name_from(streamables), action: action, target: target, targets: targets, **rendering
diff --git a/app/controllers/concerns/turbo/request_id_tracking.rb b/app/controllers/concerns/turbo/request_id_tracking.rb
new file mode 100644
index 00000000..2f41acb4
--- /dev/null
+++ b/app/controllers/concerns/turbo/request_id_tracking.rb
@@ -0,0 +1,12 @@
+module Turbo::RequestIdTracking
+ extend ActiveSupport::Concern
+
+ included do
+ around_action :turbo_tracking_request_id
+ end
+
+ private
+ def turbo_tracking_request_id(&block)
+ Turbo.with_request_id(request.headers["X-Turbo-Request-Id"], &block)
+ end
+end
diff --git a/app/helpers/turbo/drive_helper.rb b/app/helpers/turbo/drive_helper.rb
index 9aaa0a37..66e5994d 100644
--- a/app/helpers/turbo/drive_helper.rb
+++ b/app/helpers/turbo/drive_helper.rb
@@ -26,4 +26,12 @@ def turbo_exempts_page_from_preview
def turbo_page_requires_reload
provide :head, tag.meta(name: "turbo-visit-control", content: "reload")
end
+
+ def turbo_refreshes_with(method: :replace, scroll: :reset)
+ raise ArgumentError, "Invalid refresh option '#{method}'" unless method.in?(%i[ replace morph ])
+ raise ArgumentError, "Invalid scroll option '#{scroll}'" unless scroll.in?(%i[ reset preserve ])
+
+ provide :head, tag.meta(name: "turbo-refresh-method", content: method)
+ provide :head, tag.meta(name: "turbo-refresh-scroll", content: scroll)
+ end
end
diff --git a/app/helpers/turbo/streams/action_helper.rb b/app/helpers/turbo/streams/action_helper.rb
index 37e6e545..a43255a9 100644
--- a/app/helpers/turbo/streams/action_helper.rb
+++ b/app/helpers/turbo/streams/action_helper.rb
@@ -24,7 +24,7 @@ module Turbo::Streams::ActionHelper
# # =>
#
def turbo_stream_action_tag(action, target: nil, targets: nil, template: nil, **attributes)
- template = action.to_sym == :remove ? "" : tag.template(template.to_s.html_safe)
+ template = action.to_sym.in?(%i[ remove refresh ]) ? "" : tag.template(template.to_s.html_safe)
if target = convert_to_turbo_stream_dom_id(target)
tag.turbo_stream(template, **attributes, action: action, target: target)
@@ -35,6 +35,10 @@ def turbo_stream_action_tag(action, target: nil, targets: nil, template: nil, **
end
end
+ def turbo_stream_refresh_tag(**attributes)
+ turbo_stream_action_tag(:refresh, **{ "request-id": Turbo.current_request_id }.compact, **attributes)
+ end
+
private
def convert_to_turbo_stream_dom_id(target, include_selector: false)
if Array(target).any? { |value| value.respond_to?(:to_key) }
diff --git a/app/jobs/turbo/streams/broadcast_stream_job.rb b/app/jobs/turbo/streams/broadcast_stream_job.rb
new file mode 100644
index 00000000..64cd8378
--- /dev/null
+++ b/app/jobs/turbo/streams/broadcast_stream_job.rb
@@ -0,0 +1,7 @@
+class Turbo::Streams::BroadcastStreamJob < ActiveJob::Base
+ discard_on ActiveJob::DeserializationError
+
+ def perform(stream, content:)
+ Turbo::StreamsChannel.broadcast_stream_to(stream, content: content)
+ end
+end
diff --git a/app/models/concerns/turbo/broadcastable.rb b/app/models/concerns/turbo/broadcastable.rb
index d6c7fe04..62cf6c25 100644
--- a/app/models/concerns/turbo/broadcastable.rb
+++ b/app/models/concerns/turbo/broadcastable.rb
@@ -75,9 +75,32 @@
# In addition to the four basic actions, you can also use broadcast_render,
# broadcast_render_to broadcast_render_later, and broadcast_render_later_to
# to render a turbo stream template with multiple actions.
+#
+# == Suppressing broadcasts
+#
+# Sometimes, you need to disable broadcasts in certain scenarios. You can use .suppressing_turbo_broadcasts to create
+# execution contexts where broadcasts are disabled:
+#
+# class Message < ApplicationRecord
+# after_create_commit :update_message
+#
+# private
+# def update_message
+# broadcast_replace_to(user, :message, target: "message", renderable: MessageComponent.new)
+# end
+# end
+#
+# Message.suppressing_turbo_broadcasts do
+# Message.create!(board: board) # This won't broadcast the replace action
+# end
module Turbo::Broadcastable
extend ActiveSupport::Concern
+ included do
+ thread_mattr_accessor :suppressed_turbo_broadcasts, instance_accessor: false
+ delegate :suppressed_turbo_broadcasts?, to: "self.class"
+ end
+
module ClassMethods
# Configures the model to broadcast creates, updates, and destroys to a stream name derived at runtime by the
# stream symbol invocation. By default, the creates are appended to a dom id target name derived from
@@ -112,10 +135,34 @@ def broadcasts(stream = model_name.plural, inserts_by: :append, target: broadcas
after_destroy_commit -> { broadcast_remove }
end
+ # Configures the model to broadcast a "page refresh" on creates, updates, and destroys to a stream
+ # name derived at runtime by the stream symbol invocation.
+ def broadcasts_refreshes_to(stream)
+ after_commit -> { broadcast_refresh_later_to(stream.try(:call, self) || send(stream)) }
+ end
+
+ # Same as #broadcasts_refreshes_to, but the designated stream for page refreshes is automatically set to
+ # the current model.
+ def broadcasts_refreshes
+ after_commit -> { broadcast_refresh_later }
+ end
+
# All default targets will use the return of this method. Overwrite if you want something else than model_name.plural.
def broadcast_target_default
model_name.plural
end
+
+ # Executes +block+ preventing both synchronous and asynchronous broadcasts from this model.
+ def suppressing_turbo_broadcasts(&block)
+ original, self.suppressed_turbo_broadcasts = self.suppressed_turbo_broadcasts, true
+ yield
+ ensure
+ self.suppressed_turbo_broadcasts = original
+ end
+
+ def suppressed_turbo_broadcasts?
+ suppressed_turbo_broadcasts
+ end
end
# Remove this broadcastable model from the dom for subscribers of the stream name identified by the passed streamables.
@@ -124,7 +171,7 @@ def broadcast_target_default
# # Sends to the stream named "identity:2:clearances"
# clearance.broadcast_remove_to examiner.identity, :clearances
def broadcast_remove_to(*streamables, target: self)
- Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target)
+ Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_remove_to, but the designated stream is automatically set to the current model.
@@ -143,7 +190,7 @@ def broadcast_remove
# # to the stream named "identity:2:clearances"
# clearance.broadcast_replace_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
def broadcast_replace_to(*streamables, **rendering)
- Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_replace_to, but the designated stream is automatically set to the current model.
@@ -162,7 +209,7 @@ def broadcast_replace(**rendering)
# # to the stream named "identity:2:clearances"
# clearance.broadcast_update_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
def broadcast_update_to(*streamables, **rendering)
- Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_update_to, but the designated stream is automatically set to the current model.
@@ -215,7 +262,7 @@ def broadcast_after_to(*streamables, target:, **rendering)
# clearance.broadcast_append_to examiner.identity, :clearances, target: "clearances",
# partial: "clearances/other_partial", locals: { a: 1 }
def broadcast_append_to(*streamables, target: broadcast_target_default, **rendering)
- Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_append_to, but the designated stream is automatically set to the current model.
@@ -236,7 +283,7 @@ def broadcast_append(target: broadcast_target_default, **rendering)
# clearance.broadcast_prepend_to examiner.identity, :clearances, target: "clearances",
# partial: "clearances/other_partial", locals: { a: 1 }
def broadcast_prepend_to(*streamables, target: broadcast_target_default, **rendering)
- Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_prepend_to, but the designated stream is automatically set to the current model.
@@ -244,13 +291,21 @@ def broadcast_prepend(target: broadcast_target_default, **rendering)
broadcast_prepend_to self, target: target, **rendering
end
+ def broadcast_refresh_to(*streamables)
+ Turbo::StreamsChannel.broadcast_refresh_to *streamables unless suppressed_turbo_broadcasts?
+ end
+
+ def broadcast_refresh
+ broadcast_refresh_to self
+ end
+
# Broadcast a named action, allowing for dynamic dispatch, instead of using the concrete action methods. Examples:
#
# # Sends My Clearance
# # to the stream named "identity:2:clearances"
# clearance.broadcast_action_to examiner.identity, :clearances, action: :prepend, target: "clearances"
def broadcast_action_to(*streamables, action:, target: broadcast_target_default, **rendering)
- Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action, target: target, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_action_to, but the designated stream is automatically set to the current model.
@@ -261,7 +316,7 @@ def broadcast_action(action, target: broadcast_target_default, **rendering)
# Same as broadcast_replace_to but run asynchronously via a Turbo::Streams::BroadcastJob.
def broadcast_replace_later_to(*streamables, **rendering)
- Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_replace_later_to, but the designated stream is automatically set to the current model.
@@ -271,7 +326,7 @@ def broadcast_replace_later(**rendering)
# Same as broadcast_update_to but run asynchronously via a Turbo::Streams::BroadcastJob.
def broadcast_update_later_to(*streamables, **rendering)
- Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_update_later_to, but the designated stream is automatically set to the current model.
@@ -281,7 +336,7 @@ def broadcast_update_later(**rendering)
# Same as broadcast_append_to but run asynchronously via a Turbo::Streams::BroadcastJob.
def broadcast_append_later_to(*streamables, target: broadcast_target_default, **rendering)
- Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_append_later_to, but the designated stream is automatically set to the current model.
@@ -291,7 +346,7 @@ def broadcast_append_later(target: broadcast_target_default, **rendering)
# Same as broadcast_prepend_to but run asynchronously via a Turbo::Streams::BroadcastJob.
def broadcast_prepend_later_to(*streamables, target: broadcast_target_default, **rendering)
- Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_prepend_later_to, but the designated stream is automatically set to the current model.
@@ -299,9 +354,17 @@ def broadcast_prepend_later(target: broadcast_target_default, **rendering)
broadcast_prepend_later_to self, target: target, **rendering
end
+ def broadcast_refresh_later_to(*streamables, target: broadcast_target_default, **rendering)
+ Turbo::StreamsChannel.broadcast_refresh_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering).merge(request_id: Turbo.current_request_id)) unless suppressed_turbo_broadcasts?
+ end
+
+ def broadcast_refresh_later(target: broadcast_target_default, **rendering)
+ broadcast_refresh_later_to self, target: target, **rendering
+ end
+
# Same as broadcast_action_to but run asynchronously via a Turbo::Streams::BroadcastJob.
def broadcast_action_later_to(*streamables, action:, target: broadcast_target_default, **rendering)
- Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, target: target, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as #broadcast_action_later_to, but the designated stream is automatically set to the current model.
@@ -337,7 +400,7 @@ def broadcast_render(**rendering)
# desireable for model callbacks, certainly not if those callbacks are inside of a transaction. Most of the time you should
# be using `broadcast_render_later_to`, unless you specifically know why synchronous rendering is needed.
def broadcast_render_to(*streamables, **rendering)
- Turbo::StreamsChannel.broadcast_render_to(*streamables, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_render_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
# Same as broadcast_action_to but run asynchronously via a Turbo::Streams::BroadcastJob.
@@ -348,7 +411,7 @@ def broadcast_render_later(**rendering)
# Same as broadcast_render_later but run with the added option of naming the stream using the passed
# streamables.
def broadcast_render_later_to(*streamables, **rendering)
- Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering))
+ Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
@@ -361,7 +424,7 @@ def broadcast_rendering_with_defaults(options)
options.tap do |o|
# Add the current instance into the locals with the element name (which is the un-namespaced name)
# as the key. This parallels how the ActionView::ObjectRenderer would create a local variable.
- o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self)
+ o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self, request_id: Turbo.current_request_id).compact
if o[:html] || o[:partial]
return o
diff --git a/lib/turbo-rails.rb b/lib/turbo-rails.rb
index 1a50bd7c..6c401aae 100644
--- a/lib/turbo-rails.rb
+++ b/lib/turbo-rails.rb
@@ -5,6 +5,8 @@ module Turbo
mattr_accessor :draw_routes, default: true
+ thread_mattr_accessor :current_request_id
+
class << self
attr_writer :signed_stream_verifier_key
@@ -15,5 +17,12 @@ def signed_stream_verifier
def signed_stream_verifier_key
@signed_stream_verifier_key or raise ArgumentError, "Turbo requires a signed_stream_verifier_key"
end
+
+ def with_request_id(request_id)
+ old_request_id, self.current_request_id = self.current_request_id, request_id
+ yield
+ ensure
+ self.current_request_id = old_request_id
+ end
end
end
diff --git a/lib/turbo/engine.rb b/lib/turbo/engine.rb
index 487bf39b..74c4cd79 100644
--- a/lib/turbo/engine.rb
+++ b/lib/turbo/engine.rb
@@ -46,6 +46,12 @@ class Engine < Rails::Engine
end
end
+ initializer "turbo.request_id_tracking" do
+ ActiveSupport.on_load(:action_controller) do
+ include Turbo::RequestIdTracking
+ end
+ end
+
initializer "turbo.broadcastable" do
ActiveSupport.on_load(:active_record) do
include Turbo::Broadcastable
diff --git a/test/current_request_id_test.rb b/test/current_request_id_test.rb
new file mode 100644
index 00000000..9adbbad0
--- /dev/null
+++ b/test/current_request_id_test.rb
@@ -0,0 +1,28 @@
+require "test_helper"
+require "action_cable"
+
+class Turbo::CurrentRequestIdTest < ActiveSupport::TestCase
+ test "sets the current request id for a block of code" do
+ assert_nil Turbo.current_request_id
+
+ result = Turbo.with_request_id("123") do
+ assert_equal "123", Turbo.current_request_id
+ :the_result
+ end
+
+ assert_equal :the_result, result
+ assert_nil Turbo.current_request_id
+ end
+
+ test "raised errors will raise and clear the current request id" do
+ assert_nil Turbo.current_request_id
+
+ assert_raise "Some error" do
+ Turbo.with_request_id("123") do
+ raise "Some error"
+ end
+ end
+
+ assert_nil Turbo.current_request_id
+ end
+end
diff --git a/test/drive/drive_helper_test.rb b/test/drive/drive_helper_test.rb
index d22cc52d..e071ed31 100644
--- a/test/drive/drive_helper_test.rb
+++ b/test/drive/drive_helper_test.rb
@@ -10,4 +10,24 @@ class Turbo::DriveHelperTest < ActionDispatch::IntegrationTest
get trays_path
assert_match(//, @response.body)
end
+
+ test "configuring refresh strategy" do
+ get trays_path
+ assert_match(//, @response.body)
+ assert_match(//, @response.body)
+ end
+end
+
+class Turbo::DriverHelperUnitTest < ActionView::TestCase
+ include Turbo::DriveHelper
+
+ test "validate turbo refresh values" do
+ assert_raises ArgumentError do
+ turbo_refreshes_with(method: :invalid)
+ end
+
+ assert_raises ArgumentError do
+ turbo_refreshes_with(scroll: :invalid)
+ end
+ end
end
diff --git a/test/dummy/app/controllers/request_ids_controller.rb b/test/dummy/app/controllers/request_ids_controller.rb
new file mode 100644
index 00000000..5983dbde
--- /dev/null
+++ b/test/dummy/app/controllers/request_ids_controller.rb
@@ -0,0 +1,5 @@
+class RequestIdsController < ApplicationController
+ def show
+ render json: { turbo_frame_request_id: Turbo.current_request_id }
+ end
+end
diff --git a/test/dummy/app/views/trays/index.html.erb b/test/dummy/app/views/trays/index.html.erb
index c1241bd7..9658cb7d 100644
--- a/test/dummy/app/views/trays/index.html.erb
+++ b/test/dummy/app/views/trays/index.html.erb
@@ -1,4 +1,5 @@
<% turbo_exempts_page_from_cache %>
<% turbo_page_requires_reload %>
+<%= turbo_refreshes_with method: :morph, scroll: :preserve %>
Not in the cache!
diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb
index aae1d048..f84b8760 100644
--- a/test/dummy/config/routes.rb
+++ b/test/dummy/config/routes.rb
@@ -16,4 +16,5 @@
namespace :admin do
resources :companies
end
+ resource :request_id
end
diff --git a/test/refreshes/request_id_tracking_test.rb b/test/refreshes/request_id_tracking_test.rb
new file mode 100644
index 00000000..2bb34cdc
--- /dev/null
+++ b/test/refreshes/request_id_tracking_test.rb
@@ -0,0 +1,8 @@
+require "test_helper"
+
+class Turbo::RequestIdTrackingTest < ActionDispatch::IntegrationTest
+ test "set the current turbo request id from the value in the X-Turbo-Request-Id header" do
+ get request_id_path, headers: { "X-Turbo-Request-Id" => "123" }
+ assert_equal "123", JSON.parse(response.body)["turbo_frame_request_id"]
+ end
+end
diff --git a/test/streams/action_helper_test.rb b/test/streams/action_helper_test.rb
index d1818cd0..20a78836 100644
--- a/test/streams/action_helper_test.rb
+++ b/test/streams/action_helper_test.rb
@@ -85,4 +85,17 @@ class Turbo::ActionHelperTest < ActionCable::Channel::TestCase
assert_equal "", action
end
+
+ test "turbo stream refresh tag" do
+ action = turbo_stream_refresh_tag
+
+ assert_equal "", action
+ end
+
+ test "turbo stream refresh tag that carries the current request id" do
+ Turbo.current_request_id = "123"
+ action = turbo_stream_refresh_tag
+
+ assert_equal "", action
+ end
end
diff --git a/test/streams/broadcastable_test.rb b/test/streams/broadcastable_test.rb
index 69478f92..86a2ff9a 100644
--- a/test/streams/broadcastable_test.rb
+++ b/test/streams/broadcastable_test.rb
@@ -96,6 +96,18 @@ class Turbo::BroadcastableTest < ActionCable::Channel::TestCase
end
end
+ test "broadcasting refresh to stream now" do
+ assert_broadcast_on "stream", turbo_stream_refresh_tag do
+ @message.broadcast_refresh_to "stream"
+ end
+ end
+
+ test "broadcasting refresh now" do
+ assert_broadcast_on @message.to_gid_param, turbo_stream_refresh_tag do
+ @message.broadcast_refresh
+ end
+ end
+
test "broadcasting action to stream now" do
assert_broadcast_on "stream", turbo_stream_action_tag("prepend", target: "messages", template: render(@message)) do
@message.broadcast_action_to "stream", action: "prepend"
@@ -184,8 +196,8 @@ class Turbo::BroadcastableCommentTest < ActionCable::Channel::TestCase
test "updating a comment broadcasts" do
comment = @article.comments.create!(body: "random")
- stream = "#{@article.to_gid_param}:comments"
- target = "comment_#{comment.id}"
+ stream = "#{@article.to_gid_param}:comments"
+ target = "comment_#{comment.id}"
assert_broadcast_on stream, turbo_stream_action_tag("replace", target: target, template: %(precise
\n)) do
perform_enqueued_jobs do
@@ -196,11 +208,218 @@ class Turbo::BroadcastableCommentTest < ActionCable::Channel::TestCase
test "destroying a comment broadcasts" do
comment = @article.comments.create!(body: "comment")
- stream = "#{@article.to_gid_param}:comments"
- target = "comment_#{comment.id}"
+ stream = "#{@article.to_gid_param}:comments"
+ target = "comment_#{comment.id}"
assert_broadcast_on stream, turbo_stream_action_tag("remove", target: target) do
comment.destroy!
end
end
end
+
+class Turbo::SuppressingBroadcastsTest < ActionCable::Channel::TestCase
+ include ActiveJob::TestHelper, Turbo::Streams::ActionHelper
+
+ setup { @message = Message.new(id: 1, content: "Hello!") }
+
+ test "suppressing broadcasting remove to stream now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_remove_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting remove now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_remove
+ end
+ end
+
+ test "suppressing broadcasting replace to stream now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_replace_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting replace to stream later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_replace_later_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting replace now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_replace
+ end
+ end
+
+ test "suppressing broadcasting replace later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_replace_later
+ end
+ end
+
+ test "suppressing broadcasting update to stream now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_update_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting update to stream later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_update_later_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting update now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_update
+ end
+ end
+
+ test "suppressing broadcasting update later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_update_later
+ end
+ end
+
+ test "suppressing broadcasting before to stream now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_before_to "stream", target: "message_1"
+ end
+ end
+
+ test "suppressing broadcasting after to stream now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_after_to "stream", target: "message_1"
+ end
+ end
+
+ test "suppressing broadcasting append to stream now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_append_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting append to stream later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_append_later_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting append now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_append
+ end
+ end
+
+ test "suppressing broadcasting append later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_append_later
+ end
+ end
+
+ test "suppressing broadcasting prepend to stream now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_prepend_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting prepend to stream later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_prepend_later_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting refresh to stream now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_refresh_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting refresh to stream later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_refresh_later_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting prepend now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_prepend
+ end
+ end
+
+ test "suppressing broadcasting prepend later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_prepend_later
+ end
+ end
+
+ test "suppressing broadcasting action to stream now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_action_to "stream", action: "prepend"
+ end
+ end
+
+ test "suppressing broadcasting action to stream later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_action_later_to "stream", action: "prepend"
+ end
+ end
+
+ test "suppressing broadcasting action now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_action "prepend"
+ end
+ end
+
+ test "suppressing broadcasting action later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_action_later action: "prepend"
+ end
+ end
+
+ test "suppressing broadcast render now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_render
+ end
+ end
+
+ test "suppressing broadcast render later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_render_later
+ end
+ end
+
+ test "suppressing broadcast render to stream now" do
+ @profile = Users::Profile.new(id: 1, name: "Ryan")
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_render_to @profile
+ end
+ end
+
+ test "suppressing broadcast render to stream later" do
+ @profile = Users::Profile.new(id: 1, name: "Ryan")
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_render_to @profile
+ end
+ end
+
+ private
+ def assert_no_broadcasts_when_suppressing
+ assert_no_broadcasts @message.to_gid_param do
+ Message.suppressing_turbo_broadcasts do
+ yield
+ end
+ end
+ end
+
+ def assert_no_broadcasts_later_when_supressing
+ assert_no_broadcasts_when_suppressing do
+ assert_no_enqueued_jobs do
+ yield
+ end
+ end
+ end
+end
+
+
diff --git a/test/streams/streams_channel_test.rb b/test/streams/streams_channel_test.rb
index dea35c6f..9ea562f6 100644
--- a/test/streams/streams_channel_test.rb
+++ b/test/streams/streams_channel_test.rb
@@ -169,7 +169,21 @@ class Turbo::StreamsChannelTest < ActionCable::Channel::TestCase
"stream", targets: ".message", **options
end
end
+ end
+
+ test "broadcasting refresh later" do
+ assert_broadcast_on "stream", turbo_stream_refresh_tag do
+ perform_enqueued_jobs do
+ Turbo::StreamsChannel.broadcast_refresh_later_to "stream"
+ end
+ end
+ Turbo.current_request_id = "123"
+ assert_broadcast_on "stream", turbo_stream_refresh_tag do
+ perform_enqueued_jobs do
+ Turbo::StreamsChannel.broadcast_refresh_later_to "stream"
+ end
+ end
end
test "broadcasting action later" do
diff --git a/test/test_helper.rb b/test/test_helper.rb
index bc2878b8..bbe0f583 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -15,6 +15,10 @@ def render(...)
class ActiveSupport::TestCase
include ActiveJob::TestHelper
+
+ setup do
+ Turbo.current_request_id = nil
+ end
end
class ActionDispatch::IntegrationTest