turbo_rspec 1.3.0 → 1.5.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: cdd85fcc9f87814d171569e835ad85de4767daf48642d5797f15ac02decf4641
4
- data.tar.gz: a92c047884633fd146aa6806dc350edef72bd18747c39e3da618feb12223a29e
3
+ metadata.gz: d66e89583c0c374bd4b69c308390e275e70623726fd8cdc6f7f44e4df7028e06
4
+ data.tar.gz: a0be5a9653a1cfc59604b506ac2d890db6f1fd931800e21f9eccf528895d3bb8
5
5
  SHA512:
6
- metadata.gz: 7ecdc1dc0c21c083a32270e7ab085e31e5fd4e6afde0193c9f58a967e7d79aaf543387e874d8cc2fd4f1afdcb0b56a2b2b53b6c6a230f3d80a41736b68c5c01e
7
- data.tar.gz: b12927d25812f2991d889a7cedc37fe02cf4feb439adc76555dcfc8d6fd31151f9dd5cd80257ec82057d350c52e607cc2206e924bf3b159257453de9fad4ced5
6
+ metadata.gz: 49f3d0f02e97d332ed7a8c9bc28bd5c555c7ce8d969647a691e38d064afe94a77e8f0b1bd4c42b3138917c0291031aab073ae132482db60569c252acbe889c97
7
+ data.tar.gz: 41dad12a7885d0091afc1d5fa01b322f884c295b12ae753795c59ba86bbf86123281c768415c9bfa89c79d3f2812210680fcb7fd19e7f66a402db0b31cf7106e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.5.0] - 2026-06-02
4
+
5
+ ### Added
6
+
7
+ - `match_turbo_stream_snapshot` failure message now shows a line-by-line inline diff (stored → actual) instead of raw before/after strings
8
+ - `have_broadcasted_turbo_stream_to` failure message now lists all streams broadcast to the channel and shows a closest-match diff with per-constraint ✓/✗ indicators — mirrors the behaviour already present in `have_turbo_stream`
9
+ - `have_broadcasted_turbo_stream_to` now validates action names via `TurboRspec.known_actions`, raising `ArgumentError` for unrecognised actions — consistent with `have_turbo_stream`
10
+ - `have_broadcasted_turbo_stream_to.with_attributes(hash)` — assert arbitrary HTML attributes on broadcast stream elements
11
+ - `have_broadcasted_turbo_stream_to.children_only` — assert the `[children-only]` attribute on morph broadcasts
12
+
13
+ ## [1.4.0] - 2026-06-02
14
+
15
+ ### Added
16
+
17
+ - `match_turbo_stream_snapshot(name)` — records the turbo stream response on the first run and diffs against it on subsequent runs; set `UPDATE_TURBO_SNAPSHOTS=1` to overwrite
18
+ - `TurboRspec::Cop::UseHaveTurboStream` RuboCop cop — flags `expect(response.body).to include("<turbo-stream")` and similar raw-body patterns; load via `require "turbo_rspec/rubocop"` in `.rubocop.yml`
19
+ - `TurboRspec.configuration.snapshot_dir` — configure snapshot storage path (default: `spec/snapshots/turbo`)
20
+
3
21
  ## [1.3.0] - 2026-06-02
4
22
 
5
23
  ### Added
data/README.md CHANGED
@@ -183,6 +183,14 @@ expect { MyJob.perform_now }.to have_broadcasted_turbo_stream_to("notifications"
183
183
  .targeting("messages")
184
184
  .with_content("Hello")
185
185
 
186
+ # With arbitrary attributes
187
+ expect { MyJob.perform_now }.to have_broadcasted_turbo_stream_to("notifications")
188
+ .with_attributes("data-controller" => "messages")
189
+
190
+ # Turbo 8 morph with children-only
191
+ expect { MyJob.perform_now }.to have_broadcasted_turbo_stream_to("body")
192
+ .with_action(:morph).children_only
193
+
186
194
  # Count qualifiers
187
195
  expect { MyJob.perform_now }.to have_broadcasted_turbo_stream_to("notifications").once
188
196
  expect { MyJob.perform_now }.to have_broadcasted_turbo_stream_to("notifications").exactly(3).times
@@ -274,6 +282,46 @@ expect(page).to have_turbo_stream_tag("signed_stream_name")
274
282
  expect(page).not_to have_turbo_stream_tag
275
283
  ```
276
284
 
285
+ ### `match_turbo_stream_snapshot`
286
+
287
+ Record a turbo stream response on the first run and diff against it on subsequent runs. Good for complex multi-stream responses where specifying every constraint inline is noisy.
288
+
289
+ ```ruby
290
+ # First run — writes spec/snapshots/turbo/messages/new.turbo
291
+ expect(response).to match_turbo_stream_snapshot("messages/new")
292
+
293
+ # Subsequent runs — diffs against stored snapshot
294
+ expect(response).to match_turbo_stream_snapshot("messages/new")
295
+ ```
296
+
297
+ Set `UPDATE_TURBO_SNAPSHOTS=1` to overwrite an existing snapshot. Configure the storage directory:
298
+
299
+ ```ruby
300
+ # spec/support/turbo_rspec.rb
301
+ TurboRspec.configure do |config|
302
+ config.snapshot_dir = "spec/fixtures/turbo_snapshots"
303
+ end
304
+ ```
305
+
306
+ ### RuboCop cop
307
+
308
+ Load the `TurboRspec/UseHaveTurboStream` cop to catch raw `response.body` assertions:
309
+
310
+ ```yaml
311
+ # .rubocop.yml
312
+ require:
313
+ - turbo_rspec/rubocop
314
+ ```
315
+
316
+ ```ruby
317
+ # Flagged
318
+ expect(response.body).to include("<turbo-stream")
319
+ expect(response.body).to match(/turbo-stream/)
320
+
321
+ # Preferred
322
+ expect(response).to have_turbo_stream.with_action(:append)
323
+ ```
324
+
277
325
  ## Test helpers
278
326
 
279
327
  `TurboRspec::Helpers` provides factory methods for building Turbo HTML inline in tests. Auto-included in `type: :request` and `type: :controller` example groups.
data/ROADMAP.md CHANGED
@@ -2,12 +2,11 @@
2
2
 
3
3
  RSpec matchers for [Turbo](https://github.com/hotwired/turbo-rails): Turbo Streams, Turbo Frames, and ActionCable broadcasts. The goal is to replace the hand-rolled helpers that every Rails/Turbo project accumulates.
4
4
 
5
- ---
6
-
7
- ## 1.4 — Tooling
5
+ ## 1.6 — Minitest & Turbo 8 extensions
8
6
 
9
- - **RuboCop cop**flag request specs that assert `response.body` with a raw string match or `include "<turbo-stream"` instead of using the gem's matchers. Useful for migration and incremental adoption.
10
- - **Snapshot/fixture matcher** — `match_turbo_stream_snapshot("name")` records the stream on the first run and diffs on subsequent runs. Good for complex multi-stream responses where re-specifying every constraint in detail is noisy.
7
+ - **`assert_broadcasted_turbo_stream_to` / `refute_broadcasted_turbo_stream_to`** `TurboRspec::Assertions` covers request-spec stream and frame assertions but has no broadcast counterpart. Teams using Minitest with ActionCable have no parity with the RSpec broadcast matcher.
8
+ - **`have_turbo_frame` Capybara `.eager` and `.strict` chains** — `.lazy` is already supported; Turbo 8 also added `loading="eager"` and a `[strict]` boolean attribute for strict frame loading. Both are missing from the Capybara matcher.
9
+ - **Stimulus request-spec matchers** — `have_stimulus_controller`, `have_stimulus_action`, and `have_stimulus_target` currently only work in Capybara system/feature specs. Teams writing request specs can't assert Stimulus attributes on rendered HTML responses without dropping to raw string matching.
11
10
 
12
11
  ---
13
12
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module TurboRspec
6
+ # Flags request specs that assert turbo stream content by matching against
7
+ # +response.body+ as a raw string. Use +have_turbo_stream+ instead.
8
+ #
9
+ # @example Bad — string matching on response.body
10
+ # expect(response.body).to include("<turbo-stream")
11
+ # expect(response.body).to match(/turbo-stream/)
12
+ #
13
+ # @example Good
14
+ # expect(response).to have_turbo_stream
15
+ # expect(response).to have_turbo_stream.with_action(:append).targeting("list")
16
+ class UseHaveTurboStream < Base
17
+ MSG = "Use `expect(response).to have_turbo_stream` instead of " \
18
+ "asserting on `response.body` directly."
19
+
20
+ RESTRICT_ON_SEND = %i[to not_to].freeze
21
+
22
+ # Matches: expect(response.body).to <matcher>(...)
23
+ def_node_matcher :response_body_expectation?, <<~PATTERN
24
+ (send
25
+ (send nil? :expect
26
+ (send (send nil? :response) :body))
27
+ {:to :not_to}
28
+ ...)
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ return unless response_body_expectation?(node)
33
+ return unless turbo_stream_related?(node)
34
+
35
+ add_offense(node)
36
+ end
37
+
38
+ private
39
+
40
+ def turbo_stream_related?(node)
41
+ node.descendants.any? do |n|
42
+ (n.str_type? && turbo_stream_string?(n.value)) ||
43
+ (n.regexp_type? && turbo_stream_string?(n.loc.expression.source))
44
+ end
45
+ end
46
+
47
+ def turbo_stream_string?(str)
48
+ str.include?("turbo-stream") || str.include?("turbo_stream")
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -18,9 +18,16 @@ module TurboRspec
18
18
  # @return [Array<String>]
19
19
  attr_accessor :custom_actions
20
20
 
21
+ # @!attribute [rw] snapshot_dir
22
+ # Directory where turbo stream snapshots are stored.
23
+ # Defaults to +"spec/snapshots/turbo"+.
24
+ # @return [String]
25
+ attr_accessor :snapshot_dir
26
+
21
27
  def initialize
22
28
  @auto_include = true
23
29
  @custom_actions = []
30
+ @snapshot_dir = "spec/snapshots/turbo"
24
31
  end
25
32
  end
26
33
  end
@@ -12,6 +12,8 @@ module TurboRspec
12
12
  @target_all = nil
13
13
  @content = nil
14
14
  @partial = nil
15
+ @attributes = {}
16
+ @children_only = false
15
17
  @expected_count = nil
16
18
  @count_type = :at_least
17
19
  end
@@ -19,7 +21,12 @@ module TurboRspec
19
21
  # Stream constraints (mirrors HaveTurboStream)
20
22
 
21
23
  def with_action(action)
22
- @action = action.to_s
24
+ action_str = action.to_s
25
+ unless TurboRspec.known_actions.include?(action_str)
26
+ raise ArgumentError, "Unknown Turbo stream action #{action_str.inspect}. " \
27
+ "Register custom actions with TurboRspec.register_action(:#{action_str})."
28
+ end
29
+ @action = action_str
23
30
  self
24
31
  end
25
32
 
@@ -43,6 +50,16 @@ module TurboRspec
43
50
  self
44
51
  end
45
52
 
53
+ def with_attributes(attrs)
54
+ @attributes = attrs.transform_keys(&:to_s).transform_values(&:to_s)
55
+ self
56
+ end
57
+
58
+ def children_only
59
+ @children_only = true
60
+ self
61
+ end
62
+
46
63
  # Count qualifiers
47
64
 
48
65
  def once
@@ -82,7 +99,9 @@ module TurboRspec
82
99
  def matches?(block)
83
100
  before = snapshot
84
101
  block.call
85
- @matching = (snapshot - before).select { |msg| message_matches?(msg) }
102
+ @new_broadcasts = snapshot - before
103
+ @all_streams = parse_all_streams(@new_broadcasts)
104
+ @matching = @new_broadcasts.select { |msg| message_matches?(msg) }
86
105
  count_matches?(@matching.size)
87
106
  end
88
107
 
@@ -131,7 +150,9 @@ module TurboRspec
131
150
  matches_target?(stream) &&
132
151
  matches_target_all?(stream) &&
133
152
  matches_content?(stream) &&
134
- matches_partial?(stream)
153
+ matches_partial?(stream) &&
154
+ matches_attributes?(stream) &&
155
+ matches_children_only?(stream)
135
156
  end
136
157
 
137
158
  def matches_action?(stream)
@@ -156,6 +177,16 @@ module TurboRspec
156
177
  stream.to_html.include?(@partial)
157
178
  end
158
179
 
180
+ def matches_attributes?(stream)
181
+ return true if @attributes.empty?
182
+ @attributes.all? { |k, v| stream[k] == v }
183
+ end
184
+
185
+ def matches_children_only?(stream)
186
+ return true unless @children_only
187
+ !stream["children-only"].nil?
188
+ end
189
+
159
190
  def count_matches?(n)
160
191
  if @expected_count.nil?
161
192
  n >= 1
@@ -177,6 +208,8 @@ module TurboRspec
177
208
  parts << " targeting all #{@target_all.inspect}" if @target_all
178
209
  parts << " with content #{@content.inspect}" if @content
179
210
  parts << " rendering #{@partial.inspect}" if @partial
211
+ parts << " with attributes #{@attributes.inspect}" if @attributes.any?
212
+ parts << " children only" if @children_only
180
213
  parts.join
181
214
  end
182
215
 
@@ -192,11 +225,75 @@ module TurboRspec
192
225
  end
193
226
 
194
227
  def found_message
195
- if @matching.empty?
196
- "but no matching broadcasts were found"
197
- else
228
+ if @matching.size > 0
198
229
  "found #{@matching.size} matching broadcast(s)"
230
+ elsif @all_streams.empty?
231
+ "but no broadcasts were made to #{stream_name.inspect}"
232
+ else
233
+ no_match_message
234
+ end
235
+ end
236
+
237
+ def no_match_message
238
+ lines = ["found #{@all_streams.size} stream(s) broadcast to #{stream_name.inspect}:"]
239
+ @all_streams.each_with_index do |s, i|
240
+ content_preview = s.text.strip.slice(0, 50)
241
+ content_preview = content_preview.empty? ? "(empty)" : content_preview.inspect
242
+ lines << " #{i + 1}. action=#{s["action"].inspect} target=#{s["target"].inspect} content=#{content_preview}"
243
+ end
244
+
245
+ # :nocov:
246
+ if constraint_count > 0
247
+ # :nocov:
248
+ closest = @all_streams.max_by { |s| count_matching_constraints(s) }
249
+ lines << ""
250
+ lines << "closest match (#{count_matching_constraints(closest)}/#{constraint_count} constraint(s) matched):"
251
+ lines.concat(constraint_diff(closest))
252
+ end
253
+
254
+ lines.join("\n")
255
+ end
256
+
257
+ def parse_all_streams(broadcasts)
258
+ broadcasts.flat_map do |msg|
259
+ html = JSON.parse(msg)
260
+ Nokogiri::HTML5.fragment(html).css("turbo-stream").to_a
261
+ rescue JSON::ParserError
262
+ []
263
+ end
264
+ end
265
+
266
+ def count_matching_constraints(stream)
267
+ count = 0
268
+ count += 1 if !@action.nil? && matches_action?(stream)
269
+ count += 1 if !@target.nil? && matches_target?(stream)
270
+ count += 1 if !@target_all.nil? && matches_target_all?(stream)
271
+ count += 1 if !@content.nil? && matches_content?(stream)
272
+ count += 1 if !@partial.nil? && matches_partial?(stream)
273
+ count += 1 if @attributes.any? && matches_attributes?(stream)
274
+ count += 1 if @children_only && matches_children_only?(stream)
275
+ count
276
+ end
277
+
278
+ def constraint_count
279
+ [@action, @target, @target_all, @content, @partial].count { |c| !c.nil? } +
280
+ (@attributes.any? ? 1 : 0) +
281
+ (@children_only ? 1 : 0)
282
+ end
283
+
284
+ def constraint_diff(stream)
285
+ lines = []
286
+ lines << " #{matches_action?(stream) ? "✓" : "✗"} action: expected #{@action.inspect}, got #{stream["action"].inspect}" if @action
287
+ lines << " #{matches_target?(stream) ? "✓" : "✗"} target: expected #{@target.inspect}, got #{stream["target"].inspect}" if @target
288
+ lines << " #{matches_target_all?(stream) ? "✓" : "✗"} targets: expected #{@target_all.inspect}, got #{stream["targets"].inspect}" if @target_all
289
+ lines << " #{matches_content?(stream) ? "✓" : "✗"} content: expected to include #{@content.inspect}, got #{stream.text.strip.slice(0, 50).inspect}" if @content
290
+ lines << " #{matches_partial?(stream) ? "✓" : "✗"} rendering: expected to include #{@partial.inspect}" if @partial
291
+ @attributes.each do |k, v|
292
+ actual = stream[k]
293
+ lines << " #{(actual == v) ? "✓" : "✗"} attr[#{k}]: expected #{v.inspect}, got #{actual.inspect}"
199
294
  end
295
+ lines << " #{matches_children_only?(stream) ? "✓" : "✗"} children-only: expected attribute to be present" if @children_only
296
+ lines
200
297
  end
201
298
  end
202
299
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "diff/lcs"
5
+
6
+ module TurboRspec
7
+ module Matchers
8
+ # RSpec matcher that records a turbo stream response on the first run and
9
+ # diffs against the stored snapshot on subsequent runs.
10
+ #
11
+ # Snapshots are written to the directory configured by
12
+ # +TurboRspec.configuration.snapshot_dir+ (default: +spec/snapshots/turbo+).
13
+ # Each snapshot is stored as +{name}.turbo+.
14
+ #
15
+ # Set +UPDATE_TURBO_SNAPSHOTS=1+ to overwrite existing snapshots.
16
+ #
17
+ # @example
18
+ # expect(response).to match_turbo_stream_snapshot("messages/new")
19
+ class MatchTurboStreamSnapshot
20
+ def initialize(name)
21
+ @name = name
22
+ end
23
+
24
+ # @param response_or_body [#body, String]
25
+ # @return [Boolean]
26
+ def matches?(response_or_body)
27
+ @actual = extract_body(response_or_body)
28
+ @path = snapshot_path
29
+
30
+ if update_snapshots? || !File.exist?(@path)
31
+ write_snapshot(@actual)
32
+ true
33
+ else
34
+ @stored = File.read(@path)
35
+ @actual.strip == @stored.strip
36
+ end
37
+ end
38
+
39
+ # @param response_or_body [#body, String]
40
+ # @return [Boolean]
41
+ def does_not_match?(response_or_body)
42
+ !matches?(response_or_body)
43
+ end
44
+
45
+ # @return [String]
46
+ def failure_message
47
+ "expected response to match turbo stream snapshot #{@name.inspect}\n\n" \
48
+ "diff (stored → actual):\n#{inline_diff}\n\n" \
49
+ "To update: run with UPDATE_TURBO_SNAPSHOTS=1"
50
+ end
51
+
52
+ # @return [String]
53
+ def failure_message_when_negated
54
+ "expected response not to match turbo stream snapshot #{@name.inspect}"
55
+ end
56
+
57
+ # @return [String]
58
+ def description
59
+ "match turbo stream snapshot #{@name.inspect}"
60
+ end
61
+
62
+ private
63
+
64
+ def extract_body(response_or_body)
65
+ if response_or_body.respond_to?(:body)
66
+ response_or_body.body
67
+ else
68
+ response_or_body.to_s
69
+ end
70
+ end
71
+
72
+ def snapshot_path
73
+ File.join(TurboRspec.configuration.snapshot_dir, "#{@name}.turbo")
74
+ end
75
+
76
+ def write_snapshot(content)
77
+ FileUtils.mkdir_p(File.dirname(@path))
78
+ File.write(@path, content)
79
+ end
80
+
81
+ def update_snapshots?
82
+ ENV["UPDATE_TURBO_SNAPSHOTS"] == "1"
83
+ end
84
+
85
+ def inline_diff
86
+ stored_lines = @stored.strip.lines
87
+ actual_lines = @actual.strip.lines
88
+ hunks = Diff::LCS.diff(stored_lines, actual_lines)
89
+ return " (no textual difference — whitespace only?)" if hunks.empty?
90
+
91
+ lines = []
92
+ hunks.each do |hunk|
93
+ hunk.each do |change|
94
+ prefix = (change.action == "+") ? "+" : "-"
95
+ lines << "#{prefix} #{change.element.chomp}"
96
+ end
97
+ end
98
+ lines.join("\n")
99
+ end
100
+ end
101
+ end
102
+ end
@@ -4,6 +4,7 @@ require_relative "matchers/have_broadcasted_turbo_stream_to"
4
4
  require_relative "matchers/have_turbo_frame"
5
5
  require_relative "matchers/have_turbo_stream"
6
6
  require_relative "matchers/have_turbo_streams"
7
+ require_relative "matchers/match_turbo_stream_snapshot"
7
8
 
8
9
  module TurboRspec
9
10
  # RSpec matchers for Turbo Stream and Turbo Frame assertions.
@@ -46,5 +47,14 @@ module TurboRspec
46
47
  def have_turbo_streams(*matchers)
47
48
  HaveTurboStreams.new(matchers)
48
49
  end
50
+
51
+ # Assert that a response body matches a stored turbo stream snapshot.
52
+ # Creates the snapshot on the first run; diffs against it on subsequent runs.
53
+ # Set +UPDATE_TURBO_SNAPSHOTS=1+ to overwrite an existing snapshot.
54
+ # @param name [String] snapshot name, used as the file path within +snapshot_dir+
55
+ # @return [MatchTurboStreamSnapshot]
56
+ def match_turbo_stream_snapshot(name)
57
+ MatchTurboStreamSnapshot.new(name)
58
+ end
49
59
  end
50
60
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+ require_relative "../rubocop/cop/turbo_rspec/use_have_turbo_stream"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TurboRspec
4
- VERSION = "1.3.0"
4
+ VERSION = "1.5.0"
5
5
  end
data/sig/turbo_rspec.rbs CHANGED
@@ -17,6 +17,7 @@ module TurboRspec
17
17
  class Configuration
18
18
  attr_accessor auto_include: bool
19
19
  attr_accessor custom_actions: Array[String]
20
+ attr_accessor snapshot_dir: String
20
21
 
21
22
  def initialize: () -> void
22
23
  end
@@ -25,6 +26,7 @@ module TurboRspec
25
26
  def have_turbo_stream: () -> HaveTurboStream
26
27
  def assert_no_turbo_stream: () -> HaveTurboStream
27
28
  def have_turbo_streams: (*HaveTurboStream matchers) -> HaveTurboStreams
29
+ def match_turbo_stream_snapshot: (String name) -> MatchTurboStreamSnapshot
28
30
  def have_turbo_frame: () -> HaveTurboFrame
29
31
  def have_broadcasted_turbo_stream_to: (String | untyped stream_or_object) -> HaveBroadcastedTurboStreamTo
30
32
  def broadcast_turbo_stream_to: (String | untyped stream_or_object) -> HaveBroadcastedTurboStreamTo
@@ -83,6 +85,8 @@ module TurboRspec
83
85
  def targeting_all: (String selector) -> self
84
86
  def with_content: (String text) -> self
85
87
  def rendering: (String partial) -> self
88
+ def with_attributes: (Hash[String | Symbol, untyped] attrs) -> self
89
+ def children_only: () -> self
86
90
 
87
91
  # Count qualifiers
88
92
  def once: () -> self
@@ -101,6 +105,15 @@ module TurboRspec
101
105
  def description: () -> String
102
106
  end
103
107
 
108
+ class MatchTurboStreamSnapshot
109
+ def initialize: (String name) -> void
110
+ def matches?: (untyped response_or_body) -> bool
111
+ def does_not_match?: (untyped response_or_body) -> bool
112
+ def failure_message: () -> String
113
+ def failure_message_when_negated: () -> String
114
+ def description: () -> String
115
+ end
116
+
104
117
  class HaveTurboStreams
105
118
  def initialize: (Array[HaveTurboStream] expected_streams) -> void
106
119
  def matches?: (untyped response_or_body) -> bool
@@ -186,4 +199,15 @@ module TurboRspec
186
199
  end
187
200
  end
188
201
  end
202
+ end
203
+
204
+ module RuboCop
205
+ module Cop
206
+ module TurboRspec
207
+ class UseHaveTurboStream < Base
208
+ MSG: String
209
+ RESTRICT_ON_SEND: Array[Symbol]
210
+ end
211
+ end
212
+ end
189
213
  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: 1.3.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -51,6 +51,7 @@ files:
51
51
  - lib/generators/turbo_rspec/install_generator.rb
52
52
  - lib/generators/turbo_rspec/templates/README
53
53
  - lib/generators/turbo_rspec/templates/turbo_rspec.rb
54
+ - lib/rubocop/cop/turbo_rspec/use_have_turbo_stream.rb
54
55
  - lib/turbo_rspec.rb
55
56
  - lib/turbo_rspec/assertions.rb
56
57
  - lib/turbo_rspec/capybara/matchers.rb
@@ -66,6 +67,8 @@ files:
66
67
  - lib/turbo_rspec/matchers/have_turbo_frame.rb
67
68
  - lib/turbo_rspec/matchers/have_turbo_stream.rb
68
69
  - lib/turbo_rspec/matchers/have_turbo_streams.rb
70
+ - lib/turbo_rspec/matchers/match_turbo_stream_snapshot.rb
71
+ - lib/turbo_rspec/rubocop.rb
69
72
  - lib/turbo_rspec/shared_examples.rb
70
73
  - lib/turbo_rspec/version.rb
71
74
  - sig/turbo_rspec.rbs