turbo_rspec 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8cfaf54d65cd35ab2ea68138d58d70252fecf6778511beebf78ad40cfd7e998e
4
- data.tar.gz: 7bc4f9a0949b25625b59408c551d3e554ba48112b7f1c0e0f511ef5b79354878
3
+ metadata.gz: a7ce28ec048a3f308d82036741fa064f49ecdba1d9d66c7c84af42a3ef2b69e3
4
+ data.tar.gz: ef5cb3cfba998e9b7c64070eb8dbd319532c3e5584c09c7f6d5cc651ec53b71e
5
5
  SHA512:
6
- metadata.gz: 1a13ab561704b49f3659e83160724cd2afdd1def359a70f0f1057a396fc9d1f6eaba99bf4b20e54b81af62c4a1aa1ffb9b530b2565977d2cf5ea711163cd5f6d
7
- data.tar.gz: 8889b026ce58c96a12d0bf5b974dd2bdba7ce6d77520de5af6a31d45aa52a79899e9107c7b8148a1b1ce9f742ab5c1cf0da9f1999ebcd2e06120b9ee4c34b21c
6
+ metadata.gz: 6d85a8b12609e066c575f0d728ebffd0827c35fbb5b9d7572e6c7ec62493556aee058fb4dc005b1cfe9f2abe1359f5df7ef1c280bfc74b3427903aab9a8c92e5
7
+ data.tar.gz: 513effacfe8f0a663ae31456e9d82e7acb763ebc6eb04931fb536caab9d9c88752ad405ade01d79a296d8ef67e932ab6ce9e4edf0c6487b46873d9e3dfc4cab5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2026-05-28
4
+
5
+ ### Added
6
+
7
+ - `have_turbo_frame(id)` Capybara matcher for system/feature specs — asserts a `<turbo-frame>` element is on the page
8
+ - `.with_content(text)` — asserts text content within the frame
9
+ - `.loaded` — asserts the frame has the `[complete]` attribute (finished loading)
10
+ - `have_turbo_stream_tag` Capybara matcher — asserts a `<turbo-stream-source>` subscription element is present; accepts an optional signed stream name
11
+ - `within_turbo_frame(id) { }` — scopes Capybara assertions to the frame's DOM
12
+ - Auto-include `TurboRspec::Capybara::Matchers` into `type: :system` and `type: :feature` example groups when both `turbo-rails` and `capybara` are present
13
+
3
14
  ## [0.2.0] - 2026-05-28
4
15
 
5
16
  ### Added
data/README.md CHANGED
@@ -22,7 +22,10 @@ end
22
22
 
23
23
  ### Rails + turbo-rails (automatic)
24
24
 
25
- No setup needed. When `turbo-rails` is in your bundle, `TurboRspec::Matchers` is automatically included in all `type: :request` example groups.
25
+ No setup needed. When `turbo-rails` is in your bundle:
26
+
27
+ - `TurboRspec::Matchers` is automatically included in all `type: :request` example groups
28
+ - `TurboRspec::Capybara::Matchers` is automatically included in all `type: :system` and `type: :feature` example groups when `capybara` is also present
26
29
 
27
30
  ### Manual include
28
31
 
@@ -31,7 +34,8 @@ For non-Rails projects or custom contexts, include the matchers explicitly:
31
34
  ```ruby
32
35
  # spec/spec_helper.rb
33
36
  RSpec.configure do |config|
34
- config.include TurboRspec::Matchers
37
+ config.include TurboRspec::Matchers # request specs
38
+ config.include TurboRspec::Capybara::Matchers # system/feature specs
35
39
  end
36
40
  ```
37
41
 
@@ -40,7 +44,7 @@ end
40
44
  ```ruby
41
45
  # spec/support/turbo_rspec.rb
42
46
  TurboRspec.configure do |config|
43
- config.auto_include = false # disable automatic inclusion into request specs
47
+ config.auto_include = false # disable automatic inclusion
44
48
  end
45
49
  ```
46
50
 
@@ -106,6 +110,76 @@ expect(response).to have_turbo_frame.with_id("post").rendering("posts/_post")
106
110
  expect(response).not_to have_turbo_frame.with_id("notifications")
107
111
  ```
108
112
 
113
+ ### `have_broadcasted_turbo_stream_to`
114
+
115
+ Assert that a block broadcasts a `<turbo-stream>` over ActionCable. Requires ActionCable's test adapter.
116
+
117
+ ```ruby
118
+ # Basic — any broadcast to the stream
119
+ expect { MyJob.perform_now }.to have_broadcasted_turbo_stream_to("notifications")
120
+
121
+ # With constraints (same chain as have_turbo_stream)
122
+ expect { MyJob.perform_now }.to have_broadcasted_turbo_stream_to("notifications")
123
+ .with_action(:append)
124
+ .targeting("messages")
125
+ .with_content("Hello")
126
+
127
+ # Count qualifiers
128
+ expect { MyJob.perform_now }.to have_broadcasted_turbo_stream_to("notifications").once
129
+ expect { MyJob.perform_now }.to have_broadcasted_turbo_stream_to("notifications").exactly(3).times
130
+ expect { MyJob.perform_now }.to have_broadcasted_turbo_stream_to("notifications").at_least(2).times
131
+
132
+ # Alias
133
+ expect { MyJob.perform_now }.to broadcast_turbo_stream_to("notifications")
134
+
135
+ # Negation
136
+ expect { MyJob.perform_now }.not_to have_broadcasted_turbo_stream_to("notifications")
137
+ ```
138
+
139
+ ### `have_turbo_frame` (system/feature specs)
140
+
141
+ Assert that a `<turbo-frame>` element is present on the page (Capybara).
142
+
143
+ ```ruby
144
+ # Basic
145
+ expect(page).to have_turbo_frame("messages")
146
+
147
+ # With content
148
+ expect(page).to have_turbo_frame("messages").with_content("Hello")
149
+
150
+ # Loaded (frame finished loading)
151
+ expect(page).to have_turbo_frame("messages").loaded
152
+
153
+ # Negation
154
+ expect(page).not_to have_turbo_frame("notifications")
155
+ ```
156
+
157
+ ### `within_turbo_frame`
158
+
159
+ Scope Capybara assertions to a specific frame's DOM.
160
+
161
+ ```ruby
162
+ within_turbo_frame("messages") do
163
+ expect(page).to have_content("Hello")
164
+ click_button "Reply"
165
+ end
166
+ ```
167
+
168
+ ### `have_turbo_stream_tag`
169
+
170
+ Assert that a `<turbo-stream-source>` subscription element is on the page.
171
+
172
+ ```ruby
173
+ # Any stream source
174
+ expect(page).to have_turbo_stream_tag
175
+
176
+ # With signed stream name
177
+ expect(page).to have_turbo_stream_tag("signed_stream_name")
178
+
179
+ # Negation
180
+ expect(page).not_to have_turbo_stream_tag
181
+ ```
182
+
109
183
  ## Example: request spec
110
184
 
111
185
  ```ruby
@@ -136,6 +210,25 @@ RSpec.describe "Messages", type: :request do
136
210
  end
137
211
  ```
138
212
 
213
+ ## Example: system spec
214
+
215
+ ```ruby
216
+ RSpec.describe "Messages", type: :system do
217
+ it "appends a new message via Turbo Frame" do
218
+ visit messages_path
219
+ fill_in "Body", with: "Hello"
220
+ click_button "Send"
221
+
222
+ expect(page).to have_turbo_frame("messages").with_content("Hello")
223
+ end
224
+
225
+ it "shows the subscription stream tag" do
226
+ visit messages_path
227
+ expect(page).to have_turbo_stream_tag
228
+ end
229
+ end
230
+ ```
231
+
139
232
  ## Contributing
140
233
 
141
234
  Bug reports and pull requests are welcome on [GitHub](https://github.com/eclectic-coding/turbo_rspec).
data/ROADMAP.md CHANGED
@@ -5,16 +5,6 @@ RSpec matchers for [Turbo](https://github.com/hotwired/turbo-rails): Turbo Strea
5
5
  ---
6
6
 
7
7
 
8
- ## v0.3.0 — Capybara / system spec integration
9
-
10
- **Goal:** assertions that work against a live browser in feature/system specs.
11
-
12
- - `have_turbo_frame(id)` Capybara matcher — waits for the frame to appear on the page
13
- - `.with_content(...)` — delegates to Capybara's `have_content` with correct scope
14
- - `.loaded` — asserts `[complete]` attribute is present (frame finished loading)
15
- - `within_turbo_frame(id) { ... }` — scopes Capybara assertions to the frame's DOM
16
- - `have_turbo_stream_tag` — asserts a `<turbo-stream-source>` subscription element exists on the page
17
- - Docs: system spec patterns, async update testing
18
8
 
19
9
  ---
20
10
 
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurboRspec
4
+ module Capybara
5
+ module Matchers
6
+ class HaveTurboFrame
7
+ def initialize(id)
8
+ @id = id.to_s
9
+ @content = nil
10
+ @loaded = false
11
+ end
12
+
13
+ def with_content(text)
14
+ @content = text.to_s
15
+ self
16
+ end
17
+
18
+ def loaded
19
+ @loaded = true
20
+ self
21
+ end
22
+
23
+ def matches?(page_or_node)
24
+ @node = find_frame(page_or_node)
25
+ return false unless @node
26
+ return false if @loaded && !@node[:complete]
27
+ return false if @content && !@node.has_content?(@content, wait: 0)
28
+ true
29
+ end
30
+
31
+ def does_not_match?(page_or_node)
32
+ !matches?(page_or_node)
33
+ end
34
+
35
+ def failure_message
36
+ if @node.nil?
37
+ "expected page to have turbo-frame##{@id}#{constraint_description} but it was not found"
38
+ elsif @loaded && !@node[:complete]
39
+ "expected turbo-frame##{@id} to be loaded (missing [complete] attribute)"
40
+ else
41
+ "expected turbo-frame##{@id} to have content #{@content.inspect}"
42
+ end
43
+ end
44
+
45
+ def failure_message_when_negated
46
+ "expected page not to have turbo-frame##{@id}#{constraint_description}"
47
+ end
48
+
49
+ def description
50
+ "have turbo-frame##{@id}#{constraint_description}"
51
+ end
52
+
53
+ private
54
+
55
+ def find_frame(page_or_node)
56
+ page_or_node.find("turbo-frame##{@id}", wait: 0)
57
+ rescue ::Capybara::ElementNotFound
58
+ nil
59
+ end
60
+
61
+ def constraint_description
62
+ parts = []
63
+ parts << " loaded" if @loaded
64
+ parts << " with content #{@content.inspect}" if @content
65
+ parts.join
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurboRspec
4
+ module Capybara
5
+ module Matchers
6
+ class HaveTurboStreamTag
7
+ def initialize(signed_stream_name: nil)
8
+ @signed_stream_name = signed_stream_name
9
+ end
10
+
11
+ def matches?(page_or_node)
12
+ selector = build_selector
13
+ page_or_node.has_css?(selector, wait: 0)
14
+ end
15
+
16
+ def does_not_match?(page_or_node)
17
+ selector = build_selector
18
+ page_or_node.has_no_css?(selector, wait: 0)
19
+ end
20
+
21
+ def failure_message
22
+ "expected page to have a turbo-stream-source element#{stream_description}"
23
+ end
24
+
25
+ def failure_message_when_negated
26
+ "expected page not to have a turbo-stream-source element#{stream_description}"
27
+ end
28
+
29
+ def description
30
+ "have turbo-stream-source#{stream_description}"
31
+ end
32
+
33
+ private
34
+
35
+ def build_selector
36
+ if @signed_stream_name
37
+ "turbo-stream-source[src*=\"#{@signed_stream_name}\"]"
38
+ else
39
+ "turbo-stream-source"
40
+ end
41
+ end
42
+
43
+ def stream_description
44
+ @signed_stream_name ? " for #{@signed_stream_name.inspect}" : ""
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "matchers/have_turbo_frame"
4
+ require_relative "matchers/have_turbo_stream_tag"
5
+
6
+ module TurboRspec
7
+ module Capybara
8
+ module Matchers
9
+ def have_turbo_frame(id)
10
+ HaveTurboFrame.new(id)
11
+ end
12
+
13
+ def have_turbo_stream_tag(signed_stream_name = nil)
14
+ HaveTurboStreamTag.new(signed_stream_name: signed_stream_name)
15
+ end
16
+
17
+ # :nocov:
18
+ def within_turbo_frame(id, &block)
19
+ page.within("turbo-frame##{id}", &block)
20
+ end
21
+ # :nocov:
22
+ end
23
+ end
24
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TurboRspec
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/turbo_rspec.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative "turbo_rspec/version"
4
4
  require_relative "turbo_rspec/configuration"
5
5
  require_relative "turbo_rspec/matchers"
6
+ require_relative "turbo_rspec/capybara/matchers"
6
7
 
7
8
  module TurboRspec
8
9
  class Error < StandardError; end
@@ -23,6 +24,10 @@ module TurboRspec
23
24
  def install_rspec_integration(config)
24
25
  return unless configuration.auto_include && Gem.loaded_specs.key?("turbo-rails")
25
26
  config.include Matchers, type: :request
27
+ if Gem.loaded_specs.key?("capybara")
28
+ config.include Capybara::Matchers, type: :system
29
+ config.include Capybara::Matchers, type: :feature
30
+ end
26
31
  end
27
32
  end
28
33
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbo_rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -43,6 +43,9 @@ files:
43
43
  - Rakefile
44
44
  - codecov.yml
45
45
  - lib/turbo_rspec.rb
46
+ - lib/turbo_rspec/capybara/matchers.rb
47
+ - lib/turbo_rspec/capybara/matchers/have_turbo_frame.rb
48
+ - lib/turbo_rspec/capybara/matchers/have_turbo_stream_tag.rb
46
49
  - lib/turbo_rspec/configuration.rb
47
50
  - lib/turbo_rspec/matchers.rb
48
51
  - lib/turbo_rspec/matchers/have_broadcasted_turbo_stream_to.rb