Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Turbo::Broadcastable::TestHelper #466

Merged
merged 1 commit into from
Jul 25, 2023

Conversation

seanpdoyle
Copy link
Contributor

@seanpdoyle seanpdoyle commented May 13, 2023

The Turbo::Broadcastable::TestHelper concern provides Action
Cable-aware test helpers that assert that <turbo-stream> elements were
or were not broadcast over Action Cable. They are not automatically
included.

assert_turbo_stream_broadcasts

Asserts that <turbo-stream> 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 <turbo-stream> elements that are
    expected to be broadcast

Asserts <turbo-stream> elements were broadcast:

message = Message.find(1)
message.broadcast_replace_to "messages"

assert_turbo_stream_broadcasts "messages"

Asserts that two <turbo-stream> 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

assert_no_turbo_stream_broadcasts

Asserts that no <turbo-stream> 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 <turbo-stream> 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

capture_turbo_stream_broadcasts

Captures any <turbo-stream> 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 <turbo-stream> 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"]

@seanpdoyle seanpdoyle force-pushed the assert-turbo-stream-broadcasts branch from 20db59b to 4d3d59c Compare May 14, 2023 12:56
@dhh
Copy link
Member

dhh commented Jun 20, 2023

I like this, but find it a bit awkward that the assert is also responsible for a return. We typically don't have double meanings like that for assert.

How about separating out the turbo stream capturing, so we have:

actions = capture_turbo_broadcasts(stream: "messages") { message.broadcast_append_to ... }

And then explicit asserts:

assert_turbo_broadcast(action: "append", stream: "messages") { message.broadcast_append_to ... }

@seanpdoyle
Copy link
Contributor Author

The assert_turbo_broadcasts signature draws direct inspiration from assert_broadcasts, since it's built atop Action Cable's helpers.

Action Cable's assert_broadcasts test helper returns the broadcasts as objects when the assertion is passed a block:

def test_broadcasts_again
  message = assert_broadcasts('messages', 1) do
    ActionCable.server.broadcast 'messages', { text: 'hello' }
  end
  assert_equal({ text: 'hello' }, message)

  messages = assert_broadcasts('messages', 2) do
    ActionCable.server.broadcast 'messages', { text: 'hi' }
    ActionCable.server.broadcast 'messages', { text: 'how are you?' }
  end
  assert_equal 2, messages.length
  assert_equal({ text: 'hi' }, messages.first)
  assert_equal({ text: 'how are you?' }, messages.last)
end

This style of assert_broadcasts was introduced in rails/rails#47793, and has been released as part of 7.0.6.

@dhh
Copy link
Member

dhh commented Jul 24, 2023

Thanks for highlight those additions to Rails. I've made the same comments on those PRs as on this. I don't want to see assert grow side-effect behavior in any of the test helpers. But very happy to see us introduce a separate capture_* set of methods to do this.

The `Turbo::Broadcastable::TestHelper` concern provides Action
Cable-aware test helpers that assert that `<turbo-stream>` elements were
or were not broadcast over Action Cable. They are not automatically
included.

`assert_turbo_stream_broadcasts`
---

Asserts that `<turbo-stream>` 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 `<turbo-stream>` elements that are
expected to be broadcast

Asserts `<turbo-stream>` elements were broadcast:

```ruby
message = Message.find(1)
message.broadcast_replace_to "messages"

assert_turbo_stream_broadcasts "messages"
```

Asserts that two `<turbo-stream>` elements were broadcast:

```ruby
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:

```ruby
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:

```ruby
message = Message.find(1)

assert_turbo_stream_broadcasts message do
  message.broadcast_replace
end
```

`assert_no_turbo_stream_broadcasts`
---

Asserts that no `<turbo-stream>` 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 `<turbo-stream>` elements were broadcast:

```ruby
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:

```ruby
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:

```ruby
message = Message.find(1)

assert_no_turbo_stream_broadcasts message do
  # do something other than broadcast to "message_1"
end
```

`capture_turbo_stream_broadcasts`
---

Captures any `<turbo-stream>` 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 `<turbo-stream>` elements that have been broadcast as an
Array of `Nokogiri::XML::Element` instances

```ruby
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:

```ruby
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:

```ruby
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"]
```
@seanpdoyle seanpdoyle force-pushed the assert-turbo-stream-broadcasts branch from a21f55f to 85c42a9 Compare July 24, 2023 15:52
@ghiculescu
Copy link
Contributor

This style of assert_broadcasts was introduced in rails/rails#47793, and has been released as part of 7.0.6.

Unless I'm missing it, I don't think either of those PRs (rails/rails#47025 or rails/rails#47793) has been included in a release yet.

@dhh
Copy link
Member

dhh commented Jul 25, 2023

Thanks for fixing these. You're right that they were not part of a release yet, so easy to change now.

@dhh dhh merged commit 00b4f86 into hotwired:main Jul 25, 2023
@ghiculescu
Copy link
Contributor

thanks for the feedback!

@seanpdoyle seanpdoyle deleted the assert-turbo-stream-broadcasts branch July 25, 2023 13:16
@ghiculescu
Copy link
Contributor

FYI rails/rails#48934

I think the feedback about assert_broadcasts is valid, but not really a bug in Rails. Raising it here in case you want to handle it in turbo-rails.

seanpdoyle added a commit to seanpdoyle/turbo-rails that referenced this pull request Oct 3, 2024
Follow-up to [hotwired#466][]

Introduce the `#capture_turbo_stream_broadcast` helper to serve as a
shortcut invocation for `#capture_turbo_stream_broadcasts`. It returns
*a single* value, instead of an Array.

The benefit is that assertions that only need to make an assertion about
a single element are not required to deconstruct the `Array` value to
access the first element.

[hotwired#466]: hotwired#466
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants