Skip to content

Commit

Permalink
Document the assert_turbo_stream helpers (#462)
Browse files Browse the repository at this point in the history
First, mention the `assert_turbo_stream` test helpers in the
[README.md](./README.md) under a new "Testing" heading.

Next, add method-level documentation to describe each helper's
interface.

Finally, separate the `ActionDispatch::IntegrationTest` portion of the
test into a new `Turbo::TestAssertions::IntegrationTestAssertions`
module that's automatically included in
`ActionDispatch::IntegrationTest` when it's loaded.

By separating the two, the `assert_turbo_stream` helpers can be invoked
outside of an HTTP request test, while still providing a baseline
implementation for when an HTTP response is available.
  • Loading branch information
seanpdoyle authored May 8, 2023
1 parent 0661376 commit ea00f37
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 6 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ You can watch [the video introduction to Hotwire](https://hotwired.dev/#screenca

Turbo can coexist with Rails UJS, but you need to take a series of upgrade steps to make it happen. See [the upgrading guide](https://github.com/hotwired/turbo-rails/blob/main/UPGRADING.md).

## Testing


The [`Turbo::TestAssertions`](./lib/turbo/test_assertions.rb) concern provides Turbo Stream test helpers that assert the presence or absence of `<turbo-stream>` elements in a rendered fragment of HTML. `Turbo::TestAssertions` are automatically included in [`ActiveSupport::TestCase`](https://edgeapi.rubyonrails.org/classes/ActiveSupport/TestCase.html) and depend on the presence of [`rails-dom-testing`](https://github.com/rails/rails-dom-testing/) assertions.

The [`Turbo::TestAssertions::IntegrationTestAssertions`](./lib/turbo/test_assertions/integration_test_assertions.rb) are built on top of `Turbo::TestAssertions`, and add support for passing a `status:` keyword. They are automatically included in [`ActionDispatch::IntegrationTest`](https://edgeguides.rubyonrails.org/testing.html#integration-testing).


## Development

Expand Down
9 changes: 8 additions & 1 deletion lib/turbo/engine.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require "rails/engine"
require "turbo/test_assertions"

module Turbo
class Engine < Rails::Engine
Expand Down Expand Up @@ -69,8 +68,16 @@ class Engine < Rails::Engine

initializer "turbo.test_assertions" do
ActiveSupport.on_load(:active_support_test_case) do
require "turbo/test_assertions"

include Turbo::TestAssertions
end

ActiveSupport.on_load(:action_dispatch_integration_test) do
require "turbo/test_assertions/integration_test_assertions"

include Turbo::TestAssertions::IntegrationTestAssertions
end
end

initializer "turbo.integration_test_request_encoding" do
Expand Down
62 changes: 58 additions & 4 deletions lib/turbo/test_assertions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,71 @@ module TestAssertions
delegate :dom_id, :dom_class, to: ActionView::RecordIdentifier
end

def assert_turbo_stream(action:, target: nil, targets: nil, status: :ok, &block)
assert_response status
assert_equal Mime[:turbo_stream], response.media_type
# Assert that the rendered fragment of HTML contains a `<turbo-stream>`
# element.
#
# === Options
#
# * <tt>:action</tt> [String] matches the element's <tt>[action]</tt>
# attribute
# * <tt>:target</tt> [String, #to_key] matches the element's
# <tt>[target]</tt> attribute. If the value responds to <tt>#to_key</tt>,
# the value will be transformed by calling <tt>dom_id</tt>
# * <tt>:targets</tt> [String] matches the element's <tt>[targets]</tt>
# attribute
#
# Given the following HTML fragment:
#
# <turbo-stream action="remove" target="message_1"></turbo-stream>
#
# The following assertion would pass:
#
# assert_turbo_stream action: "remove", target: "message_1"
#
# You can also pass a block make assertions about the contents of the
# element. Given the following HTML fragment:
#
# <turbo-stream action="replace" target="message_1">
# <template>
# <p>Hello!</p>
# <template>
# </turbo-stream>
#
# The following assertion would pass:
#
# assert_turbo_stream action: "replace", target: "message_1" do
# assert_select "template p", text: "Hello!"
# end
#
def assert_turbo_stream(action:, target: nil, targets: nil, &block)
selector = %(turbo-stream[action="#{action}"])
selector << %([target="#{target.respond_to?(:to_key) ? dom_id(target) : target}"]) if target
selector << %([targets="#{targets}"]) if targets
assert_select selector, count: 1, &block
end

# Assert that the rendered fragment of HTML does not contain a `<turbo-stream>`
# element.
#
# === Options
#
# * <tt>:action</tt> [String] matches the element's <tt>[action]</tt>
# attribute
# * <tt>:target</tt> [String, #to_key] matches the element's
# <tt>[target]</tt> attribute. If the value responds to <tt>#to_key</tt>,
# the value will be transformed by calling <tt>dom_id</tt>
# * <tt>:targets</tt> [String] matches the element's <tt>[targets]</tt>
# attribute
#
# Given the following HTML fragment:
#
# <turbo-stream action="remove" target="message_1"></turbo-stream>
#
# The following assertion would fail:
#
# assert_no_turbo_stream action: "remove", target: "message_1"
#
def assert_no_turbo_stream(action:, target: nil, targets: nil)
assert_equal Mime[:turbo_stream], response.media_type
selector = %(turbo-stream[action="#{action}"])
selector << %([target="#{target.respond_to?(:to_key) ? dom_id(target) : target}"]) if target
selector << %([targets="#{targets}"]) if targets
Expand Down
76 changes: 76 additions & 0 deletions lib/turbo/test_assertions/integration_test_assertions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
module Turbo
module TestAssertions
module IntegrationTestAssertions
# Assert that the Turbo Stream request's response body's HTML contains a
# `<turbo-stream>` element.
#
# === Options
#
# * <tt>:status</tt> [Integer, Symbol] the HTTP response status
# * <tt>:action</tt> [String] matches the element's <tt>[action]</tt>
# attribute
# * <tt>:target</tt> [String, #to_key] matches the element's
# <tt>[target]</tt> attribute. If the value responds to <tt>#to_key</tt>,
# the value will be transformed by calling <tt>dom_id</tt>
# * <tt>:targets</tt> [String] matches the element's <tt>[targets]</tt>
# attribute
#
# Given the following HTML response body:
#
# <turbo-stream action="remove" target="message_1"></turbo-stream>
#
# The following assertion would pass:
#
# assert_turbo_stream action: "remove", target: "message_1"
#
# You can also pass a block make assertions about the contents of the
# element. Given the following HTML response body:
#
# <turbo-stream action="replace" target="message_1">
# <template>
# <p>Hello!</p>
# <template>
# </turbo-stream>
#
# The following assertion would pass:
#
# assert_turbo_stream action: "replace", target: "message_1" do
# assert_select "template p", text: "Hello!"
# end
#
def assert_turbo_stream(status: :ok, **attributes, &block)
assert_response status
assert_equal Mime[:turbo_stream], response.media_type
super(**attributes, &block)
end

# Assert that the Turbo Stream request's response body's HTML does not
# contain a `<turbo-stream>` element.
#
# === Options
#
# * <tt>:status</tt> [Integer, Symbol] the HTTP response status
# * <tt>:action</tt> [String] matches the element's <tt>[action]</tt>
# attribute
# * <tt>:target</tt> [String, #to_key] matches the element's
# <tt>[target]</tt> attribute. If the value responds to <tt>#to_key</tt>,
# the value will be transformed by calling <tt>dom_id</tt>
# * <tt>:targets</tt> [String] matches the element's <tt>[targets]</tt>
# attribute
#
# Given the following HTML response body:
#
# <turbo-stream action="remove" target="message_1"></turbo-stream>
#
# The following assertion would fail:
#
# assert_no_turbo_stream action: "remove", target: "message_1"
#
def assert_no_turbo_stream(status: :ok, **attributes)
assert_response status
assert_equal Mime[:turbo_stream], response.media_type
super(**attributes)
end
end
end
end
2 changes: 1 addition & 1 deletion test/streams/streams_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Turbo::StreamsControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to message_path(id: 1)

post messages_path, as: :turbo_stream
assert_no_turbo_stream action: :update, target: "messages"
assert_no_turbo_stream status: :created, action: :update, target: "messages"
assert_turbo_stream status: :created, action: :append, target: "messages" do |selected|
assert_equal "<template>message_1</template>", selected.children.to_html
end
Expand Down

0 comments on commit ea00f37

Please sign in to comment.