stimulus_spec 0.4.0 → 0.6.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 +17 -1
- data/README.md +18 -3
- data/ROADMAP.md +1 -1
- data/lib/stimulus_spec/capybara/matchers/have_stimulus_action.rb +14 -8
- data/lib/stimulus_spec/capybara/matchers/have_stimulus_class.rb +69 -0
- data/lib/stimulus_spec/capybara/matchers/have_stimulus_controller.rb +25 -9
- data/lib/stimulus_spec/capybara/matchers/have_stimulus_outlet.rb +69 -0
- data/lib/stimulus_spec/capybara/matchers/have_stimulus_target.rb +14 -2
- data/lib/stimulus_spec/capybara/matchers/have_stimulus_value.rb +13 -1
- data/lib/stimulus_spec/capybara/matchers.rb +2 -0
- data/lib/stimulus_spec/matchers/have_stimulus_action.rb +18 -3
- data/lib/stimulus_spec/matchers/have_stimulus_class.rb +18 -8
- data/lib/stimulus_spec/matchers/have_stimulus_controller.rb +30 -11
- data/lib/stimulus_spec/matchers/have_stimulus_outlet.rb +18 -8
- data/lib/stimulus_spec/matchers/have_stimulus_target.rb +17 -2
- data/lib/stimulus_spec/matchers/have_stimulus_value.rb +18 -8
- data/lib/stimulus_spec/version.rb +1 -1
- data/sig/stimulus_spec.rbs +37 -5
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 016bc15660c32251a12c1c9129fa88c86a3e2ac1059949535a67cdd5d64448ce
|
|
4
|
+
data.tar.gz: 76277d4b548369259e6ae9f28b8ad2b4d22637659f7c13a938e5cf2fd95127e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8502635e782f3c870ee6aa638e09cba1bbc054c731d1c4599e5b23a31551bd0fb5dce9fc71eaa3d2ca8d3713b2ab8ba26591749a59b9bcd51bb09d871d11a2cc
|
|
7
|
+
data.tar.gz: 300a7bc9351bb8d0dbcc3f70122094d03705fd876322f36187c587daa0bf44446477a57df6e2130252d3501577b1e410bd1510836e70c97729b430b7dd5a6f33
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-06-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Multi-controller assertion: `have_stimulus_controller("hello", "clipboard")` asserts multiple controllers on a single element
|
|
15
|
+
- Scoped matching via `.within(selector)` on all matchers (request-spec and Capybara)
|
|
16
|
+
|
|
17
|
+
## [0.5.0] - 2026-06-23
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Capybara `have_stimulus_class` matcher (existence and equality modes) for system/feature specs
|
|
22
|
+
- Capybara `have_stimulus_outlet` matcher (existence and selector modes) for system/feature specs
|
|
23
|
+
|
|
10
24
|
## [0.4.0] - 2026-06-22
|
|
11
25
|
|
|
12
26
|
### Added
|
|
@@ -39,7 +53,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
39
53
|
- `have_stimulus_action(descriptor)` matcher — full descriptor (`~=`) and shorthand without event (`*=`)
|
|
40
54
|
- `have_stimulus_target(controller, target)` matcher — asserts `[data-{controller}-target~="target"]`
|
|
41
55
|
|
|
42
|
-
[Unreleased]: https://github.com/eclectic-coding/stimulus_spec/compare/v0.
|
|
56
|
+
[Unreleased]: https://github.com/eclectic-coding/stimulus_spec/compare/v0.6.0...HEAD
|
|
57
|
+
[0.6.0]: https://github.com/eclectic-coding/stimulus_spec/releases/tag/v0.6.0
|
|
58
|
+
[0.5.0]: https://github.com/eclectic-coding/stimulus_spec/releases/tag/v0.5.0
|
|
43
59
|
[0.4.0]: https://github.com/eclectic-coding/stimulus_spec/releases/tag/v0.4.0
|
|
44
60
|
[0.3.0]: https://github.com/eclectic-coding/stimulus_spec/releases/tag/v0.3.0
|
|
45
61
|
[0.1.0]: https://github.com/eclectic-coding/stimulus_spec/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
Drop-in RSpec matchers for [hotwired/stimulus-rails](https://github.com/hotwired/stimulus-rails) — stop hand-rolling `data-controller` assertions and test your Stimulus wiring with expressive, purpose-built matchers.
|
|
10
10
|
|
|
11
11
|
- **Request/controller specs** — `have_stimulus_controller`, `have_stimulus_action`, `have_stimulus_target`, `have_stimulus_value`, `have_stimulus_class`, `have_stimulus_outlet`
|
|
12
|
-
- **System/feature specs** — Capybara matchers: `have_stimulus_controller`, `have_stimulus_action`, `have_stimulus_target`, `have_stimulus_value`
|
|
12
|
+
- **System/feature specs** — Capybara matchers: `have_stimulus_controller`, `have_stimulus_action`, `have_stimulus_target`, `have_stimulus_value`, `have_stimulus_class`, `have_stimulus_outlet`
|
|
13
13
|
- **Auto-included** — zero setup required when `stimulus-rails` is in your bundle
|
|
14
14
|
- **Configurable** — disable auto-include when you need manual control
|
|
15
15
|
|
|
@@ -26,6 +26,7 @@ Companion gem to [turbo_rspec](https://github.com/eclectic-coding/turbo_rspec)
|
|
|
26
26
|
- [have\_stimulus\_value](#have_stimulus_value)
|
|
27
27
|
- [have\_stimulus\_class](#have_stimulus_class)
|
|
28
28
|
- [have\_stimulus\_outlet](#have_stimulus_outlet)
|
|
29
|
+
- [Scoped matching with `.within`](#scoped-matching-with-within)
|
|
29
30
|
- [Example](#example)
|
|
30
31
|
- [Relationship to turbo\_rspec](#relationship-to-turbo_rspec)
|
|
31
32
|
- [Contributing](#contributing)
|
|
@@ -84,8 +85,8 @@ Assert that rendered HTML contains a `data-controller` attribute with the given
|
|
|
84
85
|
```ruby
|
|
85
86
|
expect(response).to have_stimulus_controller("hello")
|
|
86
87
|
|
|
87
|
-
#
|
|
88
|
-
expect(response).to have_stimulus_controller("clipboard")
|
|
88
|
+
# Assert multiple controllers on a single element
|
|
89
|
+
expect(response).to have_stimulus_controller("hello", "clipboard")
|
|
89
90
|
|
|
90
91
|
# Negation
|
|
91
92
|
expect(response).not_to have_stimulus_controller("missing")
|
|
@@ -165,6 +166,20 @@ expect(response).to have_stimulus_outlet("search", "results", "#results-list")
|
|
|
165
166
|
expect(response).not_to have_stimulus_outlet("search", "results")
|
|
166
167
|
```
|
|
167
168
|
|
|
169
|
+
### Scoped matching with `.within`
|
|
170
|
+
|
|
171
|
+
All matchers support `.within(selector)` to restrict matching to a specific part of the page:
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
# Request/controller specs — search within a CSS selector
|
|
175
|
+
expect(response).to have_stimulus_controller("search").within(".search-form")
|
|
176
|
+
expect(response).to have_stimulus_action("click->search#query").within(".search-form")
|
|
177
|
+
expect(response).to have_stimulus_target("search", "input").within(".search-form")
|
|
178
|
+
|
|
179
|
+
# System/feature specs — same API
|
|
180
|
+
expect(page).to have_stimulus_controller("search").within(".search-form")
|
|
181
|
+
```
|
|
182
|
+
|
|
168
183
|
[Back to top](#stimulusspec)
|
|
169
184
|
|
|
170
185
|
## Example
|
data/ROADMAP.md
CHANGED
|
@@ -11,7 +11,7 @@ RSpec matchers for [Stimulus](https://github.com/hotwired/stimulus-rails): contr
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
## 0.
|
|
14
|
+
## 0.7.0 — Documentation and Polish
|
|
15
15
|
|
|
16
16
|
- Full YARD documentation on all public methods and classes
|
|
17
17
|
- `docs/cookbook.md` — common patterns: request specs, system specs, multi-controller elements, typed values, outlets
|
|
@@ -8,13 +8,18 @@ module StimulusSpec
|
|
|
8
8
|
@descriptor = descriptor.to_s
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
def within(selector)
|
|
12
|
+
@scope = selector
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
|
|
11
16
|
def matches?(page)
|
|
12
17
|
@page = page
|
|
13
|
-
page.has_css?(
|
|
18
|
+
page.has_css?(scoped_selector, wait: 0)
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
def does_not_match?(page)
|
|
17
|
-
page.has_no_css?(
|
|
22
|
+
page.has_no_css?(scoped_selector, wait: 0)
|
|
18
23
|
end
|
|
19
24
|
|
|
20
25
|
def failure_message
|
|
@@ -31,12 +36,13 @@ module StimulusSpec
|
|
|
31
36
|
|
|
32
37
|
private
|
|
33
38
|
|
|
34
|
-
def
|
|
35
|
-
if @descriptor.include?("->")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
def scoped_selector
|
|
40
|
+
base = if @descriptor.include?("->")
|
|
41
|
+
"[data-action~='#{@descriptor}']"
|
|
42
|
+
else
|
|
43
|
+
"[data-action*='#{@descriptor}']"
|
|
44
|
+
end
|
|
45
|
+
@scope ? "#{@scope} #{base}" : base
|
|
40
46
|
end
|
|
41
47
|
end
|
|
42
48
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusSpec
|
|
4
|
+
module Capybara
|
|
5
|
+
module Matchers
|
|
6
|
+
class HaveStimulusClass
|
|
7
|
+
def initialize(controller, name, expected = nil)
|
|
8
|
+
@controller = controller.to_s
|
|
9
|
+
@name = name.to_s
|
|
10
|
+
@expected = expected
|
|
11
|
+
@attr = "data-#{@controller}-#{@name}-class"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def within(selector)
|
|
15
|
+
@scope = selector
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def matches?(page)
|
|
20
|
+
@page = page
|
|
21
|
+
element = page.first(scoped_selector, minimum: 0, wait: 0)
|
|
22
|
+
return false unless element
|
|
23
|
+
|
|
24
|
+
if @expected
|
|
25
|
+
@actual = element[@attr]
|
|
26
|
+
@actual == @expected.to_s
|
|
27
|
+
else
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def does_not_match?(page)
|
|
33
|
+
!matches?(page)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def failure_message
|
|
37
|
+
if @expected && @actual
|
|
38
|
+
"expected #{@attr} to be \"#{@expected}\" but was \"#{@actual}\""
|
|
39
|
+
else
|
|
40
|
+
"expected to find an element with #{@attr} on the page"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def failure_message_when_negated
|
|
45
|
+
"expected not to find an element with #{@attr} on the page"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def description
|
|
49
|
+
if @expected
|
|
50
|
+
"have Stimulus class \"#{@name}\" with \"#{@expected}\" for controller \"#{@controller}\""
|
|
51
|
+
else
|
|
52
|
+
"have Stimulus class \"#{@name}\" for controller \"#{@controller}\""
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def scoped_selector
|
|
59
|
+
base = "[#{@attr}]"
|
|
60
|
+
@scope ? "#{@scope} #{base}" : base
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def have_stimulus_class(controller, name, expected = nil)
|
|
65
|
+
HaveStimulusClass.new(controller, name, expected)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -4,34 +4,50 @@ module StimulusSpec
|
|
|
4
4
|
module Capybara
|
|
5
5
|
module Matchers
|
|
6
6
|
class HaveStimulusController
|
|
7
|
-
def initialize(
|
|
8
|
-
@
|
|
7
|
+
def initialize(*names)
|
|
8
|
+
@names = names.map(&:to_s)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def within(selector)
|
|
12
|
+
@scope = selector
|
|
13
|
+
self
|
|
9
14
|
end
|
|
10
15
|
|
|
11
16
|
def matches?(page)
|
|
12
17
|
@page = page
|
|
13
|
-
page.has_css?(
|
|
18
|
+
page.has_css?(scoped_selector, wait: 0)
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
def does_not_match?(page)
|
|
17
|
-
page.has_no_css?(
|
|
22
|
+
page.has_no_css?(scoped_selector, wait: 0)
|
|
18
23
|
end
|
|
19
24
|
|
|
20
25
|
def failure_message
|
|
21
|
-
"expected to find an element with data-controller
|
|
26
|
+
"expected to find an element with data-controller=#{label} on the page"
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
def failure_message_when_negated
|
|
25
|
-
"expected not to find an element with data-controller
|
|
30
|
+
"expected not to find an element with data-controller=#{label} on the page"
|
|
26
31
|
end
|
|
27
32
|
|
|
28
33
|
def description
|
|
29
|
-
"have Stimulus controller
|
|
34
|
+
"have Stimulus controller #{label}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def label
|
|
40
|
+
@names.map { |n| "\"#{n}\"" }.join(", ")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def scoped_selector
|
|
44
|
+
selector = @names.map { |n| "[data-controller~='#{n}']" }.join
|
|
45
|
+
@scope ? "#{@scope} #{selector}" : selector
|
|
30
46
|
end
|
|
31
47
|
end
|
|
32
48
|
|
|
33
|
-
def have_stimulus_controller(
|
|
34
|
-
HaveStimulusController.new(
|
|
49
|
+
def have_stimulus_controller(*names)
|
|
50
|
+
HaveStimulusController.new(*names)
|
|
35
51
|
end
|
|
36
52
|
end
|
|
37
53
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusSpec
|
|
4
|
+
module Capybara
|
|
5
|
+
module Matchers
|
|
6
|
+
class HaveStimulusOutlet
|
|
7
|
+
def initialize(controller, outlet, selector = nil)
|
|
8
|
+
@controller = controller.to_s
|
|
9
|
+
@outlet = outlet.to_s
|
|
10
|
+
@selector = selector
|
|
11
|
+
@attr = "data-#{@controller}-#{@outlet}-outlet"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def within(selector)
|
|
15
|
+
@scope = selector
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def matches?(page)
|
|
20
|
+
@page = page
|
|
21
|
+
element = page.first(scoped_selector, minimum: 0, wait: 0)
|
|
22
|
+
return false unless element
|
|
23
|
+
|
|
24
|
+
if @selector
|
|
25
|
+
@actual = element[@attr]
|
|
26
|
+
@actual == @selector.to_s
|
|
27
|
+
else
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def does_not_match?(page)
|
|
33
|
+
!matches?(page)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def failure_message
|
|
37
|
+
if @selector && @actual
|
|
38
|
+
"expected #{@attr} to be \"#{@selector}\" but was \"#{@actual}\""
|
|
39
|
+
else
|
|
40
|
+
"expected to find an element with #{@attr} on the page"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def failure_message_when_negated
|
|
45
|
+
"expected not to find an element with #{@attr} on the page"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def description
|
|
49
|
+
if @selector
|
|
50
|
+
"have Stimulus outlet \"#{@outlet}\" with \"#{@selector}\" for controller \"#{@controller}\""
|
|
51
|
+
else
|
|
52
|
+
"have Stimulus outlet \"#{@outlet}\" for controller \"#{@controller}\""
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def scoped_selector
|
|
59
|
+
base = "[#{@attr}]"
|
|
60
|
+
@scope ? "#{@scope} #{base}" : base
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def have_stimulus_outlet(controller, outlet, selector = nil)
|
|
65
|
+
HaveStimulusOutlet.new(controller, outlet, selector)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -9,13 +9,18 @@ module StimulusSpec
|
|
|
9
9
|
@target = target.to_s
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def within(selector)
|
|
13
|
+
@scope = selector
|
|
14
|
+
self
|
|
15
|
+
end
|
|
16
|
+
|
|
12
17
|
def matches?(page)
|
|
13
18
|
@page = page
|
|
14
|
-
page.has_css?(
|
|
19
|
+
page.has_css?(scoped_selector, wait: 0)
|
|
15
20
|
end
|
|
16
21
|
|
|
17
22
|
def does_not_match?(page)
|
|
18
|
-
page.has_no_css?(
|
|
23
|
+
page.has_no_css?(scoped_selector, wait: 0)
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
def failure_message
|
|
@@ -29,6 +34,13 @@ module StimulusSpec
|
|
|
29
34
|
def description
|
|
30
35
|
"have Stimulus target \"#{@target}\" for controller \"#{@controller}\""
|
|
31
36
|
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def scoped_selector
|
|
41
|
+
base = "[data-#{@controller}-target~='#{@target}']"
|
|
42
|
+
@scope ? "#{@scope} #{base}" : base
|
|
43
|
+
end
|
|
32
44
|
end
|
|
33
45
|
|
|
34
46
|
def have_stimulus_target(controller, target)
|
|
@@ -11,9 +11,14 @@ module StimulusSpec
|
|
|
11
11
|
@attr = "data-#{@controller}-#{@name}-value"
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
def within(selector)
|
|
15
|
+
@scope = selector
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
14
19
|
def matches?(page)
|
|
15
20
|
@page = page
|
|
16
|
-
element = page.first(
|
|
21
|
+
element = page.first(scoped_selector, minimum: 0, wait: 0)
|
|
17
22
|
return false unless element
|
|
18
23
|
|
|
19
24
|
if @expected
|
|
@@ -47,6 +52,13 @@ module StimulusSpec
|
|
|
47
52
|
"have Stimulus value \"#{@name}\" for controller \"#{@controller}\""
|
|
48
53
|
end
|
|
49
54
|
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def scoped_selector
|
|
59
|
+
base = "[#{@attr}]"
|
|
60
|
+
@scope ? "#{@scope} #{base}" : base
|
|
61
|
+
end
|
|
50
62
|
end
|
|
51
63
|
|
|
52
64
|
def have_stimulus_value(controller, name, expected = nil)
|
|
@@ -4,6 +4,8 @@ require_relative "matchers/have_stimulus_controller"
|
|
|
4
4
|
require_relative "matchers/have_stimulus_action"
|
|
5
5
|
require_relative "matchers/have_stimulus_target"
|
|
6
6
|
require_relative "matchers/have_stimulus_value"
|
|
7
|
+
require_relative "matchers/have_stimulus_class"
|
|
8
|
+
require_relative "matchers/have_stimulus_outlet"
|
|
7
9
|
|
|
8
10
|
module StimulusSpec
|
|
9
11
|
module Capybara
|
|
@@ -7,13 +7,21 @@ module StimulusSpec
|
|
|
7
7
|
@descriptor = descriptor.to_s
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
def within(selector)
|
|
11
|
+
@scope = selector
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
10
15
|
def matches?(subject)
|
|
11
16
|
@body = extract_body(subject)
|
|
12
17
|
@doc = Nokogiri::HTML5.fragment(@body)
|
|
18
|
+
root = search_root
|
|
19
|
+
return false unless root
|
|
20
|
+
|
|
13
21
|
if @descriptor.include?("->")
|
|
14
|
-
|
|
22
|
+
!root.at_css("[data-action~='#{@descriptor}']").nil?
|
|
15
23
|
else
|
|
16
|
-
|
|
24
|
+
!root.at_css("[data-action*='#{@descriptor}']").nil?
|
|
17
25
|
end
|
|
18
26
|
end
|
|
19
27
|
|
|
@@ -43,8 +51,15 @@ module StimulusSpec
|
|
|
43
51
|
subject.respond_to?(:body) ? subject.body : subject.to_s
|
|
44
52
|
end
|
|
45
53
|
|
|
54
|
+
def search_root
|
|
55
|
+
return @doc unless @scope
|
|
56
|
+
|
|
57
|
+
@doc.at_css(@scope)
|
|
58
|
+
end
|
|
59
|
+
|
|
46
60
|
def snippet
|
|
47
|
-
|
|
61
|
+
root = search_root || @doc
|
|
62
|
+
elements = root.css("[data-action]")
|
|
48
63
|
return @body if elements.empty?
|
|
49
64
|
|
|
50
65
|
elements.map(&:to_html).join("\n")
|
|
@@ -10,18 +10,21 @@ module StimulusSpec
|
|
|
10
10
|
@attr = "data-#{@controller}-#{@name}-class"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
def within(selector)
|
|
14
|
+
@scope = selector
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
13
18
|
def matches?(subject)
|
|
14
19
|
@body = extract_body(subject)
|
|
15
20
|
@doc = Nokogiri::HTML5.fragment(@body)
|
|
16
|
-
@element =
|
|
21
|
+
@element = search_root&.at_css("[#{@attr}]")
|
|
17
22
|
return false unless @element
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
true
|
|
24
|
-
end
|
|
24
|
+
return true unless @expected
|
|
25
|
+
|
|
26
|
+
@actual = @element[@attr]
|
|
27
|
+
@actual == @expected.to_s
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
def does_not_match?(subject)
|
|
@@ -54,8 +57,15 @@ module StimulusSpec
|
|
|
54
57
|
subject.respond_to?(:body) ? subject.body : subject.to_s
|
|
55
58
|
end
|
|
56
59
|
|
|
60
|
+
def search_root
|
|
61
|
+
return @doc unless @scope
|
|
62
|
+
|
|
63
|
+
@doc.at_css(@scope)
|
|
64
|
+
end
|
|
65
|
+
|
|
57
66
|
def snippet
|
|
58
|
-
|
|
67
|
+
root = search_root || @doc
|
|
68
|
+
elements = root.css("[data-controller]")
|
|
59
69
|
return @body if elements.empty?
|
|
60
70
|
|
|
61
71
|
elements.map(&:to_html).join("\n")
|
|
@@ -3,15 +3,24 @@
|
|
|
3
3
|
module StimulusSpec
|
|
4
4
|
module Matchers
|
|
5
5
|
class HaveStimulusController
|
|
6
|
-
def initialize(
|
|
7
|
-
@
|
|
6
|
+
def initialize(*names)
|
|
7
|
+
@names = names.map(&:to_s)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def within(selector)
|
|
11
|
+
@scope = selector
|
|
12
|
+
self
|
|
8
13
|
end
|
|
9
14
|
|
|
10
15
|
def matches?(subject)
|
|
11
16
|
@body = extract_body(subject)
|
|
12
17
|
@doc = Nokogiri::HTML5.fragment(@body)
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
root = search_root
|
|
19
|
+
return false unless root
|
|
20
|
+
|
|
21
|
+
@found_controllers = root.css("[data-controller]").flat_map { |el| el["data-controller"].split }
|
|
22
|
+
selector = @names.map { |n| "[data-controller~='#{n}']" }.join
|
|
23
|
+
!root.at_css(selector).nil?
|
|
15
24
|
end
|
|
16
25
|
|
|
17
26
|
def does_not_match?(subject)
|
|
@@ -19,8 +28,9 @@ module StimulusSpec
|
|
|
19
28
|
end
|
|
20
29
|
|
|
21
30
|
def failure_message
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
label = @names.map { |n| "\"#{n}\"" }.join(", ")
|
|
32
|
+
msg = "expected to find an element with data-controller=#{label}"
|
|
33
|
+
if @found_controllers&.any?
|
|
24
34
|
msg += "\n found controllers: #{@found_controllers.uniq.map { |c| "\"#{c}\"" }.join(", ")}"
|
|
25
35
|
end
|
|
26
36
|
msg += "\n in:\n#{snippet}"
|
|
@@ -28,11 +38,13 @@ module StimulusSpec
|
|
|
28
38
|
end
|
|
29
39
|
|
|
30
40
|
def failure_message_when_negated
|
|
31
|
-
|
|
41
|
+
label = @names.map { |n| "\"#{n}\"" }.join(", ")
|
|
42
|
+
"expected not to find an element with data-controller=#{label} but found one"
|
|
32
43
|
end
|
|
33
44
|
|
|
34
45
|
def description
|
|
35
|
-
|
|
46
|
+
label = @names.map { |n| "\"#{n}\"" }.join(", ")
|
|
47
|
+
"have Stimulus controller #{label}"
|
|
36
48
|
end
|
|
37
49
|
|
|
38
50
|
private
|
|
@@ -41,16 +53,23 @@ module StimulusSpec
|
|
|
41
53
|
subject.respond_to?(:body) ? subject.body : subject.to_s
|
|
42
54
|
end
|
|
43
55
|
|
|
56
|
+
def search_root
|
|
57
|
+
return @doc unless @scope
|
|
58
|
+
|
|
59
|
+
@doc.at_css(@scope)
|
|
60
|
+
end
|
|
61
|
+
|
|
44
62
|
def snippet
|
|
45
|
-
|
|
63
|
+
root = search_root || @doc
|
|
64
|
+
elements = root.css("[data-controller]")
|
|
46
65
|
return @body if elements.empty?
|
|
47
66
|
|
|
48
67
|
elements.map(&:to_html).join("\n")
|
|
49
68
|
end
|
|
50
69
|
end
|
|
51
70
|
|
|
52
|
-
def have_stimulus_controller(
|
|
53
|
-
HaveStimulusController.new(
|
|
71
|
+
def have_stimulus_controller(*names)
|
|
72
|
+
HaveStimulusController.new(*names)
|
|
54
73
|
end
|
|
55
74
|
end
|
|
56
75
|
end
|
|
@@ -10,18 +10,21 @@ module StimulusSpec
|
|
|
10
10
|
@attr = "data-#{@controller}-#{@outlet}-outlet"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
def within(selector)
|
|
14
|
+
@scope = selector
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
13
18
|
def matches?(subject)
|
|
14
19
|
@body = extract_body(subject)
|
|
15
20
|
@doc = Nokogiri::HTML5.fragment(@body)
|
|
16
|
-
@element =
|
|
21
|
+
@element = search_root&.at_css("[#{@attr}]")
|
|
17
22
|
return false unless @element
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
true
|
|
24
|
-
end
|
|
24
|
+
return true unless @selector
|
|
25
|
+
|
|
26
|
+
@actual = @element[@attr]
|
|
27
|
+
@actual == @selector.to_s
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
def does_not_match?(subject)
|
|
@@ -54,8 +57,15 @@ module StimulusSpec
|
|
|
54
57
|
subject.respond_to?(:body) ? subject.body : subject.to_s
|
|
55
58
|
end
|
|
56
59
|
|
|
60
|
+
def search_root
|
|
61
|
+
return @doc unless @scope
|
|
62
|
+
|
|
63
|
+
@doc.at_css(@scope)
|
|
64
|
+
end
|
|
65
|
+
|
|
57
66
|
def snippet
|
|
58
|
-
|
|
67
|
+
root = search_root || @doc
|
|
68
|
+
elements = root.css("[data-controller]")
|
|
59
69
|
return @body if elements.empty?
|
|
60
70
|
|
|
61
71
|
elements.map(&:to_html).join("\n")
|
|
@@ -9,10 +9,18 @@ module StimulusSpec
|
|
|
9
9
|
@attr = "data-#{@controller}-target"
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def within(selector)
|
|
13
|
+
@scope = selector
|
|
14
|
+
self
|
|
15
|
+
end
|
|
16
|
+
|
|
12
17
|
def matches?(subject)
|
|
13
18
|
@body = extract_body(subject)
|
|
14
19
|
@doc = Nokogiri::HTML5.fragment(@body)
|
|
15
|
-
|
|
20
|
+
root = search_root
|
|
21
|
+
return false unless root
|
|
22
|
+
|
|
23
|
+
!root.at_css("[#{@attr}~='#{@target}']").nil?
|
|
16
24
|
end
|
|
17
25
|
|
|
18
26
|
def does_not_match?(subject)
|
|
@@ -41,8 +49,15 @@ module StimulusSpec
|
|
|
41
49
|
subject.respond_to?(:body) ? subject.body : subject.to_s
|
|
42
50
|
end
|
|
43
51
|
|
|
52
|
+
def search_root
|
|
53
|
+
return @doc unless @scope
|
|
54
|
+
|
|
55
|
+
@doc.at_css(@scope)
|
|
56
|
+
end
|
|
57
|
+
|
|
44
58
|
def snippet
|
|
45
|
-
|
|
59
|
+
root = search_root || @doc
|
|
60
|
+
elements = root.css("[#{@attr}]")
|
|
46
61
|
return @body if elements.empty?
|
|
47
62
|
|
|
48
63
|
elements.map(&:to_html).join("\n")
|
|
@@ -10,18 +10,21 @@ module StimulusSpec
|
|
|
10
10
|
@attr = "data-#{@controller}-#{@name}-value"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
def within(selector)
|
|
14
|
+
@scope = selector
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
13
18
|
def matches?(subject)
|
|
14
19
|
@body = extract_body(subject)
|
|
15
20
|
@doc = Nokogiri::HTML5.fragment(@body)
|
|
16
|
-
@element =
|
|
21
|
+
@element = search_root&.at_css("[#{@attr}]")
|
|
17
22
|
return false unless @element
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
true
|
|
24
|
-
end
|
|
24
|
+
return true unless @expected
|
|
25
|
+
|
|
26
|
+
@actual = @element[@attr]
|
|
27
|
+
@actual == @expected.to_s
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
def does_not_match?(subject)
|
|
@@ -54,8 +57,15 @@ module StimulusSpec
|
|
|
54
57
|
subject.respond_to?(:body) ? subject.body : subject.to_s
|
|
55
58
|
end
|
|
56
59
|
|
|
60
|
+
def search_root
|
|
61
|
+
return @doc unless @scope
|
|
62
|
+
|
|
63
|
+
@doc.at_css(@scope)
|
|
64
|
+
end
|
|
65
|
+
|
|
57
66
|
def snippet
|
|
58
|
-
|
|
67
|
+
root = search_root || @doc
|
|
68
|
+
elements = root.css("[data-controller]")
|
|
59
69
|
return @body if elements.empty?
|
|
60
70
|
|
|
61
71
|
elements.map(&:to_html).join("\n")
|
data/sig/stimulus_spec.rbs
CHANGED
|
@@ -13,7 +13,7 @@ module StimulusSpec
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
module Matchers
|
|
16
|
-
def have_stimulus_controller: (String
|
|
16
|
+
def have_stimulus_controller: (*String names) -> HaveStimulusController
|
|
17
17
|
def have_stimulus_action: (String descriptor) -> HaveStimulusAction
|
|
18
18
|
def have_stimulus_target: (String controller, String target) -> HaveStimulusTarget
|
|
19
19
|
def have_stimulus_value: (String controller, String name, ?String? expected) -> HaveStimulusValue
|
|
@@ -21,7 +21,8 @@ module StimulusSpec
|
|
|
21
21
|
def have_stimulus_outlet: (String controller, String outlet, ?String? selector) -> HaveStimulusOutlet
|
|
22
22
|
|
|
23
23
|
class HaveStimulusController
|
|
24
|
-
def initialize: (String
|
|
24
|
+
def initialize: (*String names) -> void
|
|
25
|
+
def within: (String selector) -> self
|
|
25
26
|
def matches?: (untyped subject) -> bool
|
|
26
27
|
def does_not_match?: (untyped subject) -> bool
|
|
27
28
|
def failure_message: () -> String
|
|
@@ -31,6 +32,7 @@ module StimulusSpec
|
|
|
31
32
|
|
|
32
33
|
class HaveStimulusAction
|
|
33
34
|
def initialize: (String descriptor) -> void
|
|
35
|
+
def within: (String selector) -> self
|
|
34
36
|
def matches?: (untyped subject) -> bool
|
|
35
37
|
def does_not_match?: (untyped subject) -> bool
|
|
36
38
|
def failure_message: () -> String
|
|
@@ -40,6 +42,7 @@ module StimulusSpec
|
|
|
40
42
|
|
|
41
43
|
class HaveStimulusTarget
|
|
42
44
|
def initialize: (String controller, String target) -> void
|
|
45
|
+
def within: (String selector) -> self
|
|
43
46
|
def matches?: (untyped subject) -> bool
|
|
44
47
|
def does_not_match?: (untyped subject) -> bool
|
|
45
48
|
def failure_message: () -> String
|
|
@@ -49,6 +52,7 @@ module StimulusSpec
|
|
|
49
52
|
|
|
50
53
|
class HaveStimulusValue
|
|
51
54
|
def initialize: (String controller, String name, ?String? expected) -> void
|
|
55
|
+
def within: (String selector) -> self
|
|
52
56
|
def matches?: (untyped subject) -> bool
|
|
53
57
|
def does_not_match?: (untyped subject) -> bool
|
|
54
58
|
def failure_message: () -> String
|
|
@@ -58,6 +62,7 @@ module StimulusSpec
|
|
|
58
62
|
|
|
59
63
|
class HaveStimulusClass
|
|
60
64
|
def initialize: (String controller, String name, ?String? expected) -> void
|
|
65
|
+
def within: (String selector) -> self
|
|
61
66
|
def matches?: (untyped subject) -> bool
|
|
62
67
|
def does_not_match?: (untyped subject) -> bool
|
|
63
68
|
def failure_message: () -> String
|
|
@@ -67,6 +72,7 @@ module StimulusSpec
|
|
|
67
72
|
|
|
68
73
|
class HaveStimulusOutlet
|
|
69
74
|
def initialize: (String controller, String outlet, ?String? selector) -> void
|
|
75
|
+
def within: (String selector) -> self
|
|
70
76
|
def matches?: (untyped subject) -> bool
|
|
71
77
|
def does_not_match?: (untyped subject) -> bool
|
|
72
78
|
def failure_message: () -> String
|
|
@@ -77,13 +83,16 @@ module StimulusSpec
|
|
|
77
83
|
|
|
78
84
|
module Capybara
|
|
79
85
|
module Matchers
|
|
80
|
-
def have_stimulus_controller: (String
|
|
86
|
+
def have_stimulus_controller: (*String names) -> HaveStimulusController
|
|
81
87
|
def have_stimulus_action: (String descriptor) -> HaveStimulusAction
|
|
82
88
|
def have_stimulus_target: (String controller, String target) -> HaveStimulusTarget
|
|
83
89
|
def have_stimulus_value: (String controller, String name, ?String? expected) -> HaveStimulusValue
|
|
90
|
+
def have_stimulus_class: (String controller, String name, ?String? expected) -> HaveStimulusClass
|
|
91
|
+
def have_stimulus_outlet: (String controller, String outlet, ?String? selector) -> HaveStimulusOutlet
|
|
84
92
|
|
|
85
93
|
class HaveStimulusController
|
|
86
|
-
def initialize: (String
|
|
94
|
+
def initialize: (*String names) -> void
|
|
95
|
+
def within: (String selector) -> self
|
|
87
96
|
def matches?: (untyped page) -> bool
|
|
88
97
|
def does_not_match?: (untyped page) -> bool
|
|
89
98
|
def failure_message: () -> String
|
|
@@ -93,6 +102,7 @@ module StimulusSpec
|
|
|
93
102
|
|
|
94
103
|
class HaveStimulusAction
|
|
95
104
|
def initialize: (String descriptor) -> void
|
|
105
|
+
def within: (String selector) -> self
|
|
96
106
|
def matches?: (untyped page) -> bool
|
|
97
107
|
def does_not_match?: (untyped page) -> bool
|
|
98
108
|
def failure_message: () -> String
|
|
@@ -102,6 +112,7 @@ module StimulusSpec
|
|
|
102
112
|
|
|
103
113
|
class HaveStimulusTarget
|
|
104
114
|
def initialize: (String controller, String target) -> void
|
|
115
|
+
def within: (String selector) -> self
|
|
105
116
|
def matches?: (untyped page) -> bool
|
|
106
117
|
def does_not_match?: (untyped page) -> bool
|
|
107
118
|
def failure_message: () -> String
|
|
@@ -111,6 +122,27 @@ module StimulusSpec
|
|
|
111
122
|
|
|
112
123
|
class HaveStimulusValue
|
|
113
124
|
def initialize: (String controller, String name, ?String? expected) -> void
|
|
125
|
+
def within: (String selector) -> self
|
|
126
|
+
def matches?: (untyped page) -> bool
|
|
127
|
+
def does_not_match?: (untyped page) -> bool
|
|
128
|
+
def failure_message: () -> String
|
|
129
|
+
def failure_message_when_negated: () -> String
|
|
130
|
+
def description: () -> String
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class HaveStimulusClass
|
|
134
|
+
def initialize: (String controller, String name, ?String? expected) -> void
|
|
135
|
+
def within: (String selector) -> self
|
|
136
|
+
def matches?: (untyped page) -> bool
|
|
137
|
+
def does_not_match?: (untyped page) -> bool
|
|
138
|
+
def failure_message: () -> String
|
|
139
|
+
def failure_message_when_negated: () -> String
|
|
140
|
+
def description: () -> String
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class HaveStimulusOutlet
|
|
144
|
+
def initialize: (String controller, String outlet, ?String? selector) -> void
|
|
145
|
+
def within: (String selector) -> self
|
|
114
146
|
def matches?: (untyped page) -> bool
|
|
115
147
|
def does_not_match?: (untyped page) -> bool
|
|
116
148
|
def failure_message: () -> String
|
|
@@ -119,4 +151,4 @@ module StimulusSpec
|
|
|
119
151
|
end
|
|
120
152
|
end
|
|
121
153
|
end
|
|
122
|
-
end
|
|
154
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stimulus_spec
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -46,7 +46,9 @@ files:
|
|
|
46
46
|
- lib/stimulus_spec.rb
|
|
47
47
|
- lib/stimulus_spec/capybara/matchers.rb
|
|
48
48
|
- lib/stimulus_spec/capybara/matchers/have_stimulus_action.rb
|
|
49
|
+
- lib/stimulus_spec/capybara/matchers/have_stimulus_class.rb
|
|
49
50
|
- lib/stimulus_spec/capybara/matchers/have_stimulus_controller.rb
|
|
51
|
+
- lib/stimulus_spec/capybara/matchers/have_stimulus_outlet.rb
|
|
50
52
|
- lib/stimulus_spec/capybara/matchers/have_stimulus_target.rb
|
|
51
53
|
- lib/stimulus_spec/capybara/matchers/have_stimulus_value.rb
|
|
52
54
|
- lib/stimulus_spec/configuration.rb
|