turbo_rspec 0.1.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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +96 -3
- data/ROADMAP.md +0 -23
- data/lib/turbo_rspec/capybara/matchers/have_turbo_frame.rb +70 -0
- data/lib/turbo_rspec/capybara/matchers/have_turbo_stream_tag.rb +49 -0
- data/lib/turbo_rspec/capybara/matchers.rb +24 -0
- data/lib/turbo_rspec/matchers/have_broadcasted_turbo_stream_to.rb +203 -0
- data/lib/turbo_rspec/matchers.rb +7 -0
- data/lib/turbo_rspec/version.rb +1 -1
- data/lib/turbo_rspec.rb +13 -4
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7ce28ec048a3f308d82036741fa064f49ecdba1d9d66c7c84af42a3ef2b69e3
|
|
4
|
+
data.tar.gz: ef5cb3cfba998e9b7c64070eb8dbd319532c3e5584c09c7f6d5cc651ec53b71e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d85a8b12609e066c575f0d728ebffd0827c35fbb5b9d7572e6c7ec62493556aee058fb4dc005b1cfe9f2abe1359f5df7ef1c280bfc74b3427903aab9a8c92e5
|
|
7
|
+
data.tar.gz: 513effacfe8f0a663ae31456e9d82e7acb763ebc6eb04931fb536caab9d9c88752ad405ade01d79a296d8ef67e932ab6ce9e4edf0c6487b46873d9e3dfc4cab5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
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
|
+
|
|
14
|
+
## [0.2.0] - 2026-05-28
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `have_broadcasted_turbo_stream_to(stream)` block matcher for asserting ActionCable broadcasts contain a `<turbo-stream>` element
|
|
19
|
+
- Same fluent chain as `have_turbo_stream`: `.with_action`, `.targeting`, `.targeting_all`, `.with_content`, `.rendering`
|
|
20
|
+
- Count qualifiers: `.once`, `.twice`, `.exactly(n).times`, `.at_least(n).times`, `.at_most(n).times`
|
|
21
|
+
- `broadcast_turbo_stream_to` alias for naming symmetry with ActionCable's API
|
|
22
|
+
- Negation via `not_to have_broadcasted_turbo_stream_to` works out of the box
|
|
23
|
+
|
|
3
24
|
## [0.1.0] - 2026-05-28
|
|
4
25
|
|
|
5
26
|
### 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
|
|
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
|
|
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,29 +5,6 @@ RSpec matchers for [Turbo](https://github.com/hotwired/turbo-rails): Turbo Strea
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
## v0.2.0 — Broadcast matchers
|
|
9
|
-
|
|
10
|
-
**Goal:** cover the broadcast side — jobs/services that push streams over ActionCable.
|
|
11
|
-
|
|
12
|
-
- `have_broadcasted_turbo_stream_to(channel_or_object)` — wraps ActionCable's test adapter
|
|
13
|
-
- Same fluent chain as `have_turbo_stream`: `.with_action`, `.targeting`, `.rendering`, `.with_content`
|
|
14
|
-
- Count qualifiers: `.exactly(n).times`, `.at_least(n).times`, `.at_most(n).times`, `.once`, `.twice`
|
|
15
|
-
- Works inside `expect { }.to have_broadcasted_turbo_stream_to(...)` blocks
|
|
16
|
-
- Helper `broadcast_turbo_stream_to` alias for symmetry with ActionCable's naming
|
|
17
|
-
- Docs: "testing broadcasts in job specs and service specs"
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## v0.3.0 — Capybara / system spec integration
|
|
22
|
-
|
|
23
|
-
**Goal:** assertions that work against a live browser in feature/system specs.
|
|
24
|
-
|
|
25
|
-
- `have_turbo_frame(id)` Capybara matcher — waits for the frame to appear on the page
|
|
26
|
-
- `.with_content(...)` — delegates to Capybara's `have_content` with correct scope
|
|
27
|
-
- `.loaded` — asserts `[complete]` attribute is present (frame finished loading)
|
|
28
|
-
- `within_turbo_frame(id) { ... }` — scopes Capybara assertions to the frame's DOM
|
|
29
|
-
- `have_turbo_stream_tag` — asserts a `<turbo-stream-source>` subscription element exists on the page
|
|
30
|
-
- Docs: system spec patterns, async update testing
|
|
31
8
|
|
|
32
9
|
---
|
|
33
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
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module TurboRspec
|
|
6
|
+
module Matchers
|
|
7
|
+
class HaveBroadcastedTurboStreamTo
|
|
8
|
+
def initialize(stream_or_object)
|
|
9
|
+
@stream_or_object = stream_or_object
|
|
10
|
+
@action = nil
|
|
11
|
+
@target = nil
|
|
12
|
+
@target_all = nil
|
|
13
|
+
@content = nil
|
|
14
|
+
@partial = nil
|
|
15
|
+
@expected_count = nil
|
|
16
|
+
@count_type = :at_least
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Stream constraints (mirrors HaveTurboStream)
|
|
20
|
+
|
|
21
|
+
def with_action(action)
|
|
22
|
+
@action = action.to_s
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def targeting(dom_id)
|
|
27
|
+
@target = dom_id.to_s
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def targeting_all(selector)
|
|
32
|
+
@target_all = selector.to_s
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def with_content(text)
|
|
37
|
+
@content = text.to_s
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def rendering(partial)
|
|
42
|
+
@partial = partial.to_s
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Count qualifiers
|
|
47
|
+
|
|
48
|
+
def once
|
|
49
|
+
exactly(1)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def twice
|
|
53
|
+
exactly(2)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def exactly(n)
|
|
57
|
+
@expected_count = n
|
|
58
|
+
@count_type = :exactly
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def at_least(n)
|
|
63
|
+
@expected_count = n
|
|
64
|
+
@count_type = :at_least
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def at_most(n)
|
|
69
|
+
@expected_count = n
|
|
70
|
+
@count_type = :at_most
|
|
71
|
+
self
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def times
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def supports_block_expectations?
|
|
79
|
+
true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def matches?(block)
|
|
83
|
+
before = snapshot
|
|
84
|
+
block.call
|
|
85
|
+
@matching = (snapshot - before).select { |msg| message_matches?(msg) }
|
|
86
|
+
count_matches?(@matching.size)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def does_not_match?(block)
|
|
90
|
+
!matches?(block)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def failure_message
|
|
94
|
+
"expected block to broadcast a turbo stream to #{stream_name.inspect}#{constraint_description}#{count_description}\n#{found_message}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def failure_message_when_negated
|
|
98
|
+
"expected block not to broadcast a turbo stream to #{stream_name.inspect}#{constraint_description}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def description
|
|
102
|
+
"broadcast a turbo stream to #{stream_name.inspect}#{constraint_description}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def stream_name
|
|
108
|
+
@stream_name ||= if @stream_or_object.respond_to?(:to_str)
|
|
109
|
+
@stream_or_object
|
|
110
|
+
elsif defined?(Turbo::StreamsChannel)
|
|
111
|
+
Turbo::StreamsChannel.broadcasting_for(@stream_or_object)
|
|
112
|
+
else
|
|
113
|
+
@stream_or_object.to_s
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def snapshot
|
|
118
|
+
ActionCable.server.pubsub.broadcasts(stream_name).dup
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def message_matches?(message)
|
|
122
|
+
html = JSON.parse(message)
|
|
123
|
+
streams = Nokogiri::HTML5.fragment(html).css("turbo-stream")
|
|
124
|
+
streams.any? { |stream| stream_matches?(stream) }
|
|
125
|
+
rescue JSON::ParserError
|
|
126
|
+
false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def stream_matches?(stream)
|
|
130
|
+
matches_action?(stream) &&
|
|
131
|
+
matches_target?(stream) &&
|
|
132
|
+
matches_target_all?(stream) &&
|
|
133
|
+
matches_content?(stream) &&
|
|
134
|
+
matches_partial?(stream)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def matches_action?(stream)
|
|
138
|
+
@action.nil? || stream["action"] == @action
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def matches_target?(stream)
|
|
142
|
+
@target.nil? || stream["target"] == @target
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def matches_target_all?(stream)
|
|
146
|
+
@target_all.nil? || stream["targets"] == @target_all
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def matches_content?(stream)
|
|
150
|
+
return true if @content.nil?
|
|
151
|
+
stream.text.include?(@content)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def matches_partial?(stream)
|
|
155
|
+
return true if @partial.nil?
|
|
156
|
+
stream.to_html.include?(@partial)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def count_matches?(n)
|
|
160
|
+
if @expected_count.nil?
|
|
161
|
+
n >= 1
|
|
162
|
+
else
|
|
163
|
+
# :nocov:
|
|
164
|
+
case @count_type
|
|
165
|
+
# :nocov:
|
|
166
|
+
when :exactly then n == @expected_count
|
|
167
|
+
when :at_least then n >= @expected_count
|
|
168
|
+
when :at_most then n <= @expected_count
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def constraint_description
|
|
174
|
+
parts = []
|
|
175
|
+
parts << " with action #{@action.inspect}" if @action
|
|
176
|
+
parts << " targeting #{@target.inspect}" if @target
|
|
177
|
+
parts << " targeting all #{@target_all.inspect}" if @target_all
|
|
178
|
+
parts << " with content #{@content.inspect}" if @content
|
|
179
|
+
parts << " rendering #{@partial.inspect}" if @partial
|
|
180
|
+
parts.join
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def count_description
|
|
184
|
+
return "" if @expected_count.nil? && @count_type == :at_least
|
|
185
|
+
# :nocov:
|
|
186
|
+
case @count_type
|
|
187
|
+
# :nocov:
|
|
188
|
+
when :exactly then " exactly #{@expected_count} time(s)"
|
|
189
|
+
when :at_least then " at least #{@expected_count} time(s)"
|
|
190
|
+
when :at_most then " at most #{@expected_count} time(s)"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def found_message
|
|
195
|
+
if @matching.empty?
|
|
196
|
+
"but no matching broadcasts were found"
|
|
197
|
+
else
|
|
198
|
+
"found #{@matching.size} matching broadcast(s)"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
data/lib/turbo_rspec/matchers.rb
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "matchers/have_broadcasted_turbo_stream_to"
|
|
3
4
|
require_relative "matchers/have_turbo_frame"
|
|
4
5
|
require_relative "matchers/have_turbo_stream"
|
|
5
6
|
|
|
6
7
|
module TurboRspec
|
|
7
8
|
module Matchers
|
|
9
|
+
def have_broadcasted_turbo_stream_to(stream_or_object)
|
|
10
|
+
HaveBroadcastedTurboStreamTo.new(stream_or_object)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
alias_method :broadcast_turbo_stream_to, :have_broadcasted_turbo_stream_to
|
|
14
|
+
|
|
8
15
|
def have_turbo_frame
|
|
9
16
|
HaveTurboFrame.new
|
|
10
17
|
end
|
data/lib/turbo_rspec/version.rb
CHANGED
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
|
|
@@ -19,12 +20,20 @@ module TurboRspec
|
|
|
19
20
|
def reset_configuration!
|
|
20
21
|
@configuration = Configuration.new
|
|
21
22
|
end
|
|
23
|
+
|
|
24
|
+
def install_rspec_integration(config)
|
|
25
|
+
return unless configuration.auto_include && Gem.loaded_specs.key?("turbo-rails")
|
|
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
|
|
31
|
+
end
|
|
22
32
|
end
|
|
23
33
|
end
|
|
24
34
|
|
|
35
|
+
# :nocov:
|
|
25
36
|
if defined?(RSpec)
|
|
26
|
-
RSpec.configure
|
|
27
|
-
config.include TurboRspec::Matchers, type: :request if TurboRspec.configuration.auto_include &&
|
|
28
|
-
Gem.loaded_specs.key?("turbo-rails")
|
|
29
|
-
end
|
|
37
|
+
RSpec.configure { |config| TurboRspec.install_rspec_integration(config) }
|
|
30
38
|
end
|
|
39
|
+
# :nocov:
|
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -43,8 +43,12 @@ 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
|
|
51
|
+
- lib/turbo_rspec/matchers/have_broadcasted_turbo_stream_to.rb
|
|
48
52
|
- lib/turbo_rspec/matchers/have_turbo_frame.rb
|
|
49
53
|
- lib/turbo_rspec/matchers/have_turbo_stream.rb
|
|
50
54
|
- lib/turbo_rspec/version.rb
|