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 # # 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