turbo_rspec 1.2.0 → 1.4.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: 7341e9dfd6be8235a2269e94ef9923e470aa5da675ccd52f8d507f42c3950398
4
- data.tar.gz: 65f4f2bb0aa32c4032d4ba2daad8c9442e20d056b3a455b0937eaeaf380f924a
3
+ metadata.gz: 0a09c91b6fcf16983b8ebbdf20dac6bfb350a9fea07e1779541a05200ba9fc16
4
+ data.tar.gz: 43d963aacdbc42a3d90ff131953418c07063e78537bec4cabde4f9189416f06f
5
5
  SHA512:
6
- metadata.gz: b433e046578d5f3914a95cd443ed32020bff526bddf3bf79eea34ef7bc52a7ce06823ac25714f309842c357d97e0e70edd208b75a85c8e0a118e7d8b86b36c9f
7
- data.tar.gz: 44d25746b61d50d932f74f92b2f6a07c50c70a6d320c452f302693b507617da38facf8fca1d0ac61f66cf01a49187e8288a20c6182b27cbe4ab91db42fe28ec5
6
+ metadata.gz: 5d01faa2f37db0c9ea82322fd3445a70a8c0201f9176a0f578f6d87a84954ce2f0dbecc5ccaeb6a347aad02d1d47ab16c2fb9568eb0b0b19a2230a6f06917267
7
+ data.tar.gz: a3958b07e62ff57af1d72540364df29e374b912752a16c0faf7a3533ed8a08f4fb51dc54ad691c3a67fae39f3aac378f40f16af3d3b567650fc469d076db1707
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.4.0] - 2026-06-02
4
+
5
+ ### Added
6
+
7
+ - `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
8
+ - `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`
9
+ - `TurboRspec.configuration.snapshot_dir` — configure snapshot storage path (default: `spec/snapshots/turbo`)
10
+
11
+ ## [1.3.0] - 2026-06-02
12
+
13
+ ### Added
14
+
15
+ - `have_stimulus_controller(name)` — Capybara matcher asserting `data-controller` contains the given controller name
16
+ - `have_stimulus_action(descriptor)` — Capybara matcher asserting `data-action` contains the descriptor; accepts a full descriptor (`click->hello#greet`) or shorthand without event (`hello#greet`)
17
+ - `have_stimulus_target(controller, target)` — Capybara matcher asserting `data-{controller}-target` contains the target name
18
+
3
19
  ## [1.2.0] - 2026-06-01
4
20
 
5
21
  ### Added
data/README.md CHANGED
@@ -10,7 +10,7 @@ Drop-in test matchers for [hotwired/turbo-rails](https://github.com/hotwired/tur
10
10
 
11
11
  - **Request/controller specs** — `have_turbo_stream`, `have_turbo_frame`, `have_turbo_streams`
12
12
  - **Broadcast specs** — `have_broadcasted_turbo_stream_to` with count qualifiers
13
- - **System/feature specs** — Capybara matchers: `have_turbo_frame`, `have_turbo_stream_tag`, `within_turbo_frame`
13
+ - **System/feature specs** — Capybara matchers: `have_turbo_frame`, `have_turbo_stream_tag`, `within_turbo_frame`, `have_stimulus_controller`, `have_stimulus_action`, `have_stimulus_target`
14
14
  - **Minitest** — `assert_turbo_stream`, `refute_turbo_stream`, `assert_turbo_frame`, `refute_turbo_frame`
15
15
  - **Factory helpers** — `turbo_stream_html`, `turbo_frame_html`
16
16
  - **Shared examples** — `it_behaves_like "a turbo stream response"`
@@ -236,6 +236,29 @@ within_turbo_frame("messages") do
236
236
  end
237
237
  ```
238
238
 
239
+ ### Stimulus matchers
240
+
241
+ Assert Stimulus controller, action, and target attributes on the page (Capybara).
242
+
243
+ ```ruby
244
+ # Controller — data-controller contains "hello"
245
+ expect(page).to have_stimulus_controller("hello")
246
+
247
+ # Action — full descriptor
248
+ expect(page).to have_stimulus_action("click->hello#greet")
249
+
250
+ # Action — shorthand without event (matches any event prefix)
251
+ expect(page).to have_stimulus_action("hello#greet")
252
+
253
+ # Target — data-hello-target contains "name"
254
+ expect(page).to have_stimulus_target("hello", "name")
255
+
256
+ # Negation
257
+ expect(page).not_to have_stimulus_controller("missing")
258
+ ```
259
+
260
+ All three matchers use space-separated token matching (`~=`), so they work correctly when multiple controllers, actions, or targets are declared on a single element.
261
+
239
262
  ### `have_turbo_stream_tag`
240
263
 
241
264
  Assert that a `<turbo-stream-source>` subscription element is on the page.
@@ -251,6 +274,46 @@ expect(page).to have_turbo_stream_tag("signed_stream_name")
251
274
  expect(page).not_to have_turbo_stream_tag
252
275
  ```
253
276
 
277
+ ### `match_turbo_stream_snapshot`
278
+
279
+ 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.
280
+
281
+ ```ruby
282
+ # First run — writes spec/snapshots/turbo/messages/new.turbo
283
+ expect(response).to match_turbo_stream_snapshot("messages/new")
284
+
285
+ # Subsequent runs — diffs against stored snapshot
286
+ expect(response).to match_turbo_stream_snapshot("messages/new")
287
+ ```
288
+
289
+ Set `UPDATE_TURBO_SNAPSHOTS=1` to overwrite an existing snapshot. Configure the storage directory:
290
+
291
+ ```ruby
292
+ # spec/support/turbo_rspec.rb
293
+ TurboRspec.configure do |config|
294
+ config.snapshot_dir = "spec/fixtures/turbo_snapshots"
295
+ end
296
+ ```
297
+
298
+ ### RuboCop cop
299
+
300
+ Load the `TurboRspec/UseHaveTurboStream` cop to catch raw `response.body` assertions:
301
+
302
+ ```yaml
303
+ # .rubocop.yml
304
+ require:
305
+ - turbo_rspec/rubocop
306
+ ```
307
+
308
+ ```ruby
309
+ # Flagged
310
+ expect(response.body).to include("<turbo-stream")
311
+ expect(response.body).to match(/turbo-stream/)
312
+
313
+ # Preferred
314
+ expect(response).to have_turbo_stream.with_action(:append)
315
+ ```
316
+
254
317
  ## Test helpers
255
318
 
256
319
  `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
@@ -4,17 +4,6 @@ RSpec matchers for [Turbo](https://github.com/hotwired/turbo-rails): Turbo Strea
4
4
 
5
5
  ---
6
6
 
7
- ## 1.3 — Stimulus companion
8
-
9
- - **Stimulus matchers** — `have_stimulus_controller`, `have_stimulus_action`, `have_stimulus_target` Capybara matchers. turbo-rails ships with Stimulus; teams using both already test Stimulus behavior manually. This is the natural next surface area for a complete Hotwire testing toolkit.
10
-
11
- ## 1.4 — Tooling
12
-
13
- - **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.
14
- - **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.
15
-
16
- ---
17
-
18
7
  ## Guiding principles
19
8
 
20
9
  - **Zero magic by default.** Auto-include only when it's unambiguous (Rails request specs). Everything else is opt-in.
@@ -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
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurboRspec
4
+ module Capybara
5
+ module Matchers
6
+ # Capybara matcher asserting that an element with the given Stimulus
7
+ # action descriptor is present on the page (+data-action+ contains the
8
+ # descriptor as a space-separated token).
9
+ #
10
+ # Accepts either a full descriptor (+click->hello#greet+) or a shorthand
11
+ # without an event (+hello#greet+), which matches any event prefix.
12
+ #
13
+ # @example Full descriptor
14
+ # expect(page).to have_stimulus_action("click->hello#greet")
15
+ #
16
+ # @example Shorthand (any event)
17
+ # expect(page).to have_stimulus_action("hello#greet")
18
+ class HaveStimulusAction
19
+ def initialize(action_descriptor)
20
+ @action_descriptor = action_descriptor.to_s
21
+ end
22
+
23
+ def matches?(page_or_node)
24
+ page_or_node.has_css?(selector, wait: 0)
25
+ end
26
+
27
+ def does_not_match?(page_or_node)
28
+ page_or_node.has_no_css?(selector, wait: 0)
29
+ end
30
+
31
+ def failure_message
32
+ "expected page to have Stimulus action #{@action_descriptor.inspect}"
33
+ end
34
+
35
+ def failure_message_when_negated
36
+ "expected page not to have Stimulus action #{@action_descriptor.inspect}"
37
+ end
38
+
39
+ def description
40
+ "have Stimulus action #{@action_descriptor.inspect}"
41
+ end
42
+
43
+ private
44
+
45
+ def selector
46
+ if @action_descriptor.include?("->")
47
+ "[data-action~='#{@action_descriptor}']"
48
+ else
49
+ "[data-action*='#{@action_descriptor}']"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurboRspec
4
+ module Capybara
5
+ module Matchers
6
+ # Capybara matcher asserting that an element with the given Stimulus
7
+ # controller is present on the page (+data-controller+ contains the name
8
+ # as a space-separated token).
9
+ #
10
+ # @example
11
+ # expect(page).to have_stimulus_controller("hello")
12
+ # expect(page).not_to have_stimulus_controller("missing")
13
+ class HaveStimulusController
14
+ def initialize(controller_name)
15
+ @controller_name = controller_name.to_s
16
+ end
17
+
18
+ def matches?(page_or_node)
19
+ page_or_node.has_css?(selector, wait: 0)
20
+ end
21
+
22
+ def does_not_match?(page_or_node)
23
+ page_or_node.has_no_css?(selector, wait: 0)
24
+ end
25
+
26
+ def failure_message
27
+ "expected page to have Stimulus controller #{@controller_name.inspect}"
28
+ end
29
+
30
+ def failure_message_when_negated
31
+ "expected page not to have Stimulus controller #{@controller_name.inspect}"
32
+ end
33
+
34
+ def description
35
+ "have Stimulus controller #{@controller_name.inspect}"
36
+ end
37
+
38
+ private
39
+
40
+ def selector
41
+ "[data-controller~='#{@controller_name}']"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurboRspec
4
+ module Capybara
5
+ module Matchers
6
+ # Capybara matcher asserting that an element with the given Stimulus
7
+ # target is present on the page. Checks +data-{controller}-target+
8
+ # contains the target name as a space-separated token.
9
+ #
10
+ # @example
11
+ # expect(page).to have_stimulus_target("hello", "name")
12
+ # expect(page).not_to have_stimulus_target("hello", "missing")
13
+ class HaveStimulusTarget
14
+ def initialize(controller_name, target_name)
15
+ @controller_name = controller_name.to_s
16
+ @target_name = target_name.to_s
17
+ end
18
+
19
+ def matches?(page_or_node)
20
+ page_or_node.has_css?(selector, wait: 0)
21
+ end
22
+
23
+ def does_not_match?(page_or_node)
24
+ page_or_node.has_no_css?(selector, wait: 0)
25
+ end
26
+
27
+ def failure_message
28
+ "expected page to have Stimulus target #{@target_name.inspect} for controller #{@controller_name.inspect}"
29
+ end
30
+
31
+ def failure_message_when_negated
32
+ "expected page not to have Stimulus target #{@target_name.inspect} for controller #{@controller_name.inspect}"
33
+ end
34
+
35
+ def description
36
+ "have Stimulus target #{@target_name.inspect} for #{@controller_name.inspect}"
37
+ end
38
+
39
+ private
40
+
41
+ def selector
42
+ "[data-#{@controller_name}-target~='#{@target_name}']"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -2,6 +2,9 @@
2
2
 
3
3
  require_relative "matchers/have_turbo_frame"
4
4
  require_relative "matchers/have_turbo_stream_tag"
5
+ require_relative "matchers/have_stimulus_controller"
6
+ require_relative "matchers/have_stimulus_action"
7
+ require_relative "matchers/have_stimulus_target"
5
8
 
6
9
  module TurboRspec
7
10
  module Capybara
@@ -14,6 +17,18 @@ module TurboRspec
14
17
  HaveTurboStreamTag.new(signed_stream_name: signed_stream_name)
15
18
  end
16
19
 
20
+ def have_stimulus_controller(controller_name)
21
+ HaveStimulusController.new(controller_name)
22
+ end
23
+
24
+ def have_stimulus_action(action_descriptor)
25
+ HaveStimulusAction.new(action_descriptor)
26
+ end
27
+
28
+ def have_stimulus_target(controller_name, target_name)
29
+ HaveStimulusTarget.new(controller_name, target_name)
30
+ end
31
+
17
32
  # :nocov:
18
33
  def within_turbo_frame(id, &block)
19
34
  page.within("turbo-frame##{id}", &block)
@@ -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
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module TurboRspec
6
+ module Matchers
7
+ # RSpec matcher that records a turbo stream response on the first run and
8
+ # diffs against the stored snapshot on subsequent runs.
9
+ #
10
+ # Snapshots are written to the directory configured by
11
+ # +TurboRspec.configuration.snapshot_dir+ (default: +spec/snapshots/turbo+).
12
+ # Each snapshot is stored as +{name}.turbo+.
13
+ #
14
+ # Set +UPDATE_TURBO_SNAPSHOTS=1+ to overwrite existing snapshots.
15
+ #
16
+ # @example
17
+ # expect(response).to match_turbo_stream_snapshot("messages/new")
18
+ class MatchTurboStreamSnapshot
19
+ def initialize(name)
20
+ @name = name
21
+ end
22
+
23
+ # @param response_or_body [#body, String]
24
+ # @return [Boolean]
25
+ def matches?(response_or_body)
26
+ @actual = extract_body(response_or_body)
27
+ @path = snapshot_path
28
+
29
+ if update_snapshots? || !File.exist?(@path)
30
+ write_snapshot(@actual)
31
+ true
32
+ else
33
+ @stored = File.read(@path)
34
+ @actual.strip == @stored.strip
35
+ end
36
+ end
37
+
38
+ # @param response_or_body [#body, String]
39
+ # @return [Boolean]
40
+ def does_not_match?(response_or_body)
41
+ !matches?(response_or_body)
42
+ end
43
+
44
+ # @return [String]
45
+ def failure_message
46
+ "expected response to match turbo stream snapshot #{@name.inspect}\n\n" \
47
+ "stored:\n#{@stored}\n\n" \
48
+ "actual:\n#{@actual}\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
+ end
85
+ end
86
+ 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.2.0"
4
+ VERSION = "1.4.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
@@ -101,6 +103,15 @@ module TurboRspec
101
103
  def description: () -> String
102
104
  end
103
105
 
106
+ class MatchTurboStreamSnapshot
107
+ def initialize: (String name) -> void
108
+ def matches?: (untyped response_or_body) -> bool
109
+ def does_not_match?: (untyped response_or_body) -> bool
110
+ def failure_message: () -> String
111
+ def failure_message_when_negated: () -> String
112
+ def description: () -> String
113
+ end
114
+
104
115
  class HaveTurboStreams
105
116
  def initialize: (Array[HaveTurboStream] expected_streams) -> void
106
117
  def matches?: (untyped response_or_body) -> bool
@@ -127,6 +138,9 @@ module TurboRspec
127
138
  module Matchers
128
139
  def have_turbo_frame: (String id) -> HaveTurboFrame
129
140
  def have_turbo_stream_tag: (?String? signed_stream_name) -> HaveTurboStreamTag
141
+ def have_stimulus_controller: (String controller_name) -> HaveStimulusController
142
+ def have_stimulus_action: (String action_descriptor) -> HaveStimulusAction
143
+ def have_stimulus_target: (String controller_name, String target_name) -> HaveStimulusTarget
130
144
  def within_turbo_frame: (String id) { () -> void } -> void
131
145
 
132
146
  class HaveTurboFrame
@@ -146,6 +160,33 @@ module TurboRspec
146
160
  def description: () -> String
147
161
  end
148
162
 
163
+ class HaveStimulusController
164
+ def initialize: (String controller_name) -> void
165
+ def matches?: (untyped page_or_node) -> bool
166
+ def does_not_match?: (untyped page_or_node) -> bool
167
+ def failure_message: () -> String
168
+ def failure_message_when_negated: () -> String
169
+ def description: () -> String
170
+ end
171
+
172
+ class HaveStimulusAction
173
+ def initialize: (String action_descriptor) -> void
174
+ def matches?: (untyped page_or_node) -> bool
175
+ def does_not_match?: (untyped page_or_node) -> bool
176
+ def failure_message: () -> String
177
+ def failure_message_when_negated: () -> String
178
+ def description: () -> String
179
+ end
180
+
181
+ class HaveStimulusTarget
182
+ def initialize: (String controller_name, String target_name) -> void
183
+ def matches?: (untyped page_or_node) -> bool
184
+ def does_not_match?: (untyped page_or_node) -> bool
185
+ def failure_message: () -> String
186
+ def failure_message_when_negated: () -> String
187
+ def description: () -> String
188
+ end
189
+
149
190
  class HaveTurboStreamTag
150
191
  def initialize: (?signed_stream_name: String?) -> void
151
192
  def matches?: (untyped page_or_node) -> bool
@@ -156,4 +197,15 @@ module TurboRspec
156
197
  end
157
198
  end
158
199
  end
200
+ end
201
+
202
+ module RuboCop
203
+ module Cop
204
+ module TurboRspec
205
+ class UseHaveTurboStream < Base
206
+ MSG: String
207
+ RESTRICT_ON_SEND: Array[Symbol]
208
+ end
209
+ end
210
+ end
159
211
  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.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -51,9 +51,13 @@ 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
58
+ - lib/turbo_rspec/capybara/matchers/have_stimulus_action.rb
59
+ - lib/turbo_rspec/capybara/matchers/have_stimulus_controller.rb
60
+ - lib/turbo_rspec/capybara/matchers/have_stimulus_target.rb
57
61
  - lib/turbo_rspec/capybara/matchers/have_turbo_frame.rb
58
62
  - lib/turbo_rspec/capybara/matchers/have_turbo_stream_tag.rb
59
63
  - lib/turbo_rspec/configuration.rb
@@ -63,6 +67,8 @@ files:
63
67
  - lib/turbo_rspec/matchers/have_turbo_frame.rb
64
68
  - lib/turbo_rspec/matchers/have_turbo_stream.rb
65
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
66
72
  - lib/turbo_rspec/shared_examples.rb
67
73
  - lib/turbo_rspec/version.rb
68
74
  - sig/turbo_rspec.rbs