module Turbo module Broadcastable module TestHelper extend ActiveSupport::Concern included do include ActionCable::TestHelper include Turbo::Streams::StreamName end # Asserts that `` elements were broadcast over Action Cable # # === Arguments # # * stream_name_or_object the objects used to generate the # channel Action Cable name, or the name itself # * &block optional block executed before the # assertion # # === Options # # * count: the number of `` elements that are # expected to be broadcast # # Asserts `` elements were broadcast: # # message = Message.find(1) # message.broadcast_replace_to "messages" # # assert_turbo_stream_broadcasts "messages" # # Asserts that two `` elements were broadcast: # # message = Message.find(1) # message.broadcast_replace_to "messages" # message.broadcast_remove_to "messages" # # assert_turbo_stream_broadcasts "messages", count: 2 # # You can pass a block to run before the assertion: # # message = Message.find(1) # # assert_turbo_stream_broadcasts "messages" do # message.broadcast_append_to "messages" # end # # In addition to a String, the helper also accepts an Object or Array to # determine the name of the channel the elements are broadcast to: # # message = Message.find(1) # # assert_turbo_stream_broadcasts message do # message.broadcast_replace # end # def assert_turbo_stream_broadcasts(stream_name_or_object, count: nil, &block) payloads = capture_turbo_stream_broadcasts(stream_name_or_object, &block) stream_name = stream_name_from(stream_name_or_object) if count.nil? assert_not_empty payloads, "Expected at least one broadcast on #{stream_name.inspect}, but there were none" else broadcasts = "Turbo Stream broadcast".pluralize(count) assert count == payloads.count, "Expected #{count} #{broadcasts} on #{stream_name.inspect}, but there were none" end end # Asserts that no `` elements were broadcast over Action Cable # # === Arguments # # * stream_name_or_object the objects used to generate the # channel Action Cable name, or the name itself # * &block optional block executed before the # assertion # # Asserts that no `` elements were broadcast: # # message = Message.find(1) # message.broadcast_replace_to "messages" # # assert_no_turbo_stream_broadcasts "messages" # fails with MiniTest::Assertion error # # You can pass a block to run before the assertion: # # message = Message.find(1) # # assert_no_turbo_stream_broadcasts "messages" do # # do something other than broadcast to "messages" # end # # In addition to a String, the helper also accepts an Object or Array to # determine the name of the channel the elements are broadcast to: # # message = Message.find(1) # # assert_no_turbo_stream_broadcasts message do # # do something other than broadcast to "message_1" # end # def assert_no_turbo_stream_broadcasts(stream_name_or_object, &block) block&.call stream_name = stream_name_from(stream_name_or_object) payloads = broadcasts(stream_name) assert payloads.empty?, "Expected no broadcasts on #{stream_name.inspect}, but there were #{payloads.count}" end # Captures any `` elements that were broadcast over Action Cable # # === Arguments # # * stream_name_or_object the objects used to generate the # channel Action Cable name, or the name itself # * &block optional block to capture broadcasts during execution # # Returns any `` elements that have been broadcast as an # Array of Nokogiri::XML::Element instances # # message = Message.find(1) # message.broadcast_append_to "messages" # message.broadcast_prepend_to "messages" # # turbo_streams = capture_turbo_stream_broadcasts "messages" # # assert_equal "append", turbo_streams.first["action"] # assert_equal "prepend", turbo_streams.second["action"] # # You can pass a block to limit the scope of the broadcasts being captured: # # message = Message.find(1) # # turbo_streams = capture_turbo_stream_broadcasts "messages" do # message.broadcast_append_to "messages" # end # # assert_equal "append", turbo_streams.first["action"] # # In addition to a String, the helper also accepts an Object or Array to # determine the name of the channel the elements are broadcast to: # # message = Message.find(1) # # replace, remove = capture_turbo_stream_broadcasts message do # message.broadcast_replace # message.broadcast_remove # end # # assert_equal "replace", replace["action"] # assert_equal "replace", remove["action"] # def capture_turbo_stream_broadcasts(stream_name_or_object, &block) block&.call stream_name = stream_name_from(stream_name_or_object) payloads = broadcasts(stream_name) payloads.flat_map do |payload| html = ActiveSupport::JSON.decode(payload) document = Nokogiri::HTML5.parse(html) document.at("body").element_children end end end end end