rubocop-capybara 2.22.1 → 2.23.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 -0
- data/config/default.yml +59 -43
- data/config/obsoletion.yml +18 -0
- data/lib/rubocop/capybara/version.rb +1 -1
- data/lib/rubocop/cop/capybara/assert_style.rb +30 -0
- data/lib/rubocop/cop/capybara/find_all_first.rb +16 -2
- data/lib/rubocop/cop/capybara/mixin/css_selector.rb +12 -0
- data/lib/rubocop/cop/capybara/redundant_within_find.rb +17 -4
- data/lib/rubocop/cop/capybara/rspec/current_path_expectation.rb +146 -0
- data/lib/rubocop/cop/capybara/rspec/have_content.rb +51 -0
- data/lib/rubocop/cop/capybara/rspec/have_selector.rb +2 -1
- data/lib/rubocop/cop/capybara/rspec/match_style.rb +53 -0
- data/lib/rubocop/cop/capybara/rspec/negation_matcher.rb +106 -0
- data/lib/rubocop/cop/capybara/rspec/negation_matcher_after_visit.rb +53 -0
- data/lib/rubocop/cop/capybara/rspec/specific_matcher.rb +96 -0
- data/lib/rubocop/cop/capybara/rspec/visibility_matcher.rb +72 -0
- data/lib/rubocop/cop/capybara_cops.rb +8 -6
- data/lib/rubocop-capybara.rb +1 -1
- metadata +14 -17
- data/lib/rubocop/cop/capybara/current_path_expectation.rb +0 -141
- data/lib/rubocop/cop/capybara/match_style.rb +0 -58
- data/lib/rubocop/cop/capybara/negation_matcher.rb +0 -104
- data/lib/rubocop/cop/capybara/negation_matcher_after_visit.rb +0 -51
- data/lib/rubocop/cop/capybara/specific_matcher.rb +0 -94
- data/lib/rubocop/cop/capybara/visibility_matcher.rb +0 -71
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4d1c9d6e62bd38cf7ef34d42dcf3eabcc49cbb2ad1cb23ea96ddfb6d7b964ef0
|
|
4
|
+
data.tar.gz: 5fe88f1d98cb8b1068c9604648f1ef18517fcd27b3552a7525913f5bcd30490d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f1652a43814b536fd98975128b51468b453f2665f32f97e8eb8ba9940ff92b7dd03093d29a738356bf50e11349d6b11c7e225cc23e4306bac1d94da48fd0948
|
|
7
|
+
data.tar.gz: 9045ef2ed4d00354ca9888158c6dc6088361f476aef5105dfb120421951252d101470cdd68bdcaf71b0ae2fbebe265fab86f6b1ca0fb863603b5465f8a5c3ccb
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## Edge (Unreleased)
|
|
4
4
|
|
|
5
|
+
## 2.23.0 (2026-04-30)
|
|
6
|
+
|
|
7
|
+
- Bump RuboCop requirement to +1.81. ([@ydah])
|
|
8
|
+
- Add new `Capybara/RSpec/HaveContent` cop. ([@nzlaura])
|
|
9
|
+
- Move the department associated with `Capybara::RSpecMatchers` to `Capybara/RSpec/*`. ([@ydah])
|
|
10
|
+
- Rename `Capybara/CurrentPathExpectation` to `Capybara/RSpec/CurrentPathExpectation`
|
|
11
|
+
- Rename `Capybara/SpecificMatcher` to `Capybara/RSpec/SpecificMatcher`
|
|
12
|
+
- Rename `Capybara/NegationMatcher` to `Capybara/RSpec/NegationMatcher`
|
|
13
|
+
- Rename `Capybara/NegationMatcherAfterVisit` to `Capybara/RSpec/NegationMatcherAfterVisit`
|
|
14
|
+
- Rename `Capybara/VisibilityMatcher` to `Capybara/RSpec/VisibilityMatcher`
|
|
15
|
+
- Split `Capybara/MatchStyle` into `Capybara/AssertStyle` and `Capybara/RSpec/MatchStyle`. ([@ydah])
|
|
16
|
+
- Fix a false positive for `Capybara/FindAllFirst` when using logical operators with `all('...')[0]`. ([@ydah])
|
|
17
|
+
- Fix an incorrect autocorrect for `Capybara/FindAllFirst` when `find` or `all` with `match: :first` uses a receiver and mark autocorrection as unsafe. ([@ydah])
|
|
18
|
+
- Fix an error for `Capybara/RSpec/HaveSelector` when passing only a selector type. ([@ydah])
|
|
19
|
+
|
|
5
20
|
## 2.22.1 (2025-03-12)
|
|
6
21
|
|
|
7
22
|
- Fix incorrect plugin version. ([@koic])
|
|
@@ -13,6 +28,7 @@
|
|
|
13
28
|
- Add a new `Capybara/NegationMatcherAfterVisit` cop. ([@ydah])
|
|
14
29
|
- Fix an error for `Capybara/RSpec/HaveSelector` when passing no arguments. ([@earlopain])
|
|
15
30
|
- Make RuboCop Capybara work as a RuboCop plugin. ([@bquorning])
|
|
31
|
+
- Fix an incorrect autocorrect for `Capybara/RedundantWithinFind` when escape required css selector. ([@ydah])
|
|
16
32
|
|
|
17
33
|
## 2.21.0 (2024-06-08)
|
|
18
34
|
|
|
@@ -89,6 +105,7 @@
|
|
|
89
105
|
[@darhazer]: https://github.com/Darhazer
|
|
90
106
|
[@earlopain]: https://github.com/earlopain
|
|
91
107
|
[@koic]: https://github.com/koic
|
|
108
|
+
[@nzlaura]: https://github.com/nzlaura
|
|
92
109
|
[@onumis]: https://github.com/onumis
|
|
93
110
|
[@oskarsezerins]: https://github.com/OskarsEzerins
|
|
94
111
|
[@pirj]: https://github.com/pirj
|
data/config/default.yml
CHANGED
|
@@ -15,6 +15,12 @@ Capybara/AmbiguousClick:
|
|
|
15
15
|
VersionAdded: '2.22'
|
|
16
16
|
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/AmbiguousClick
|
|
17
17
|
|
|
18
|
+
Capybara/AssertStyle:
|
|
19
|
+
Description: Checks for usage of deprecated assert style method.
|
|
20
|
+
Enabled: pending
|
|
21
|
+
VersionAdded: '2.23'
|
|
22
|
+
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/AssertStyle
|
|
23
|
+
|
|
18
24
|
Capybara/ClickLinkOrButtonStyle:
|
|
19
25
|
Description: Checks for methods of button or link clicks.
|
|
20
26
|
Enabled: false
|
|
@@ -26,42 +32,14 @@ Capybara/ClickLinkOrButtonStyle:
|
|
|
26
32
|
- strict
|
|
27
33
|
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/ClickLinkOrButtonStyle
|
|
28
34
|
|
|
29
|
-
Capybara/CurrentPathExpectation:
|
|
30
|
-
Description: Checks that no expectations are set on Capybara's `current_path`.
|
|
31
|
-
Enabled: true
|
|
32
|
-
VersionAdded: '1.18'
|
|
33
|
-
VersionChanged: '2.0'
|
|
34
|
-
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/CurrentPathExpectation
|
|
35
|
-
|
|
36
35
|
Capybara/FindAllFirst:
|
|
37
36
|
Description: Enforces use of `first` instead of `all` with `first` or `[0]`.
|
|
38
37
|
Enabled: pending
|
|
38
|
+
SafeAutoCorrect: false
|
|
39
39
|
VersionAdded: '2.22'
|
|
40
|
+
VersionChanged: '2.23'
|
|
40
41
|
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/FindAllFirst
|
|
41
42
|
|
|
42
|
-
Capybara/MatchStyle:
|
|
43
|
-
Description: Checks for usage of deprecated style methods.
|
|
44
|
-
Enabled: pending
|
|
45
|
-
VersionAdded: '2.17'
|
|
46
|
-
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/MatchStyle
|
|
47
|
-
|
|
48
|
-
Capybara/NegationMatcher:
|
|
49
|
-
Description: Enforces use of `have_no_*` or `not_to` for negated expectations.
|
|
50
|
-
Enabled: pending
|
|
51
|
-
VersionAdded: '2.14'
|
|
52
|
-
VersionChanged: '2.20'
|
|
53
|
-
EnforcedStyle: have_no
|
|
54
|
-
SupportedStyles:
|
|
55
|
-
- have_no
|
|
56
|
-
- not_to
|
|
57
|
-
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcher
|
|
58
|
-
|
|
59
|
-
Capybara/NegationMatcherAfterVisit:
|
|
60
|
-
Description: Do not allow negative matchers to be used immediately after `visit`.
|
|
61
|
-
Enabled: pending
|
|
62
|
-
VersionAdded: '2.22'
|
|
63
|
-
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcherAfterVisit
|
|
64
|
-
|
|
65
43
|
Capybara/RedundantWithinFind:
|
|
66
44
|
Description: Checks for redundant `within find(...)` calls.
|
|
67
45
|
Enabled: pending
|
|
@@ -80,23 +58,23 @@ Capybara/SpecificFinders:
|
|
|
80
58
|
VersionAdded: '2.13'
|
|
81
59
|
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/SpecificFinders
|
|
82
60
|
|
|
83
|
-
Capybara/SpecificMatcher:
|
|
84
|
-
Description: Checks for there is a more specific matcher offered by Capybara.
|
|
85
|
-
Enabled: pending
|
|
86
|
-
VersionAdded: '2.12'
|
|
87
|
-
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/SpecificMatcher
|
|
88
|
-
|
|
89
|
-
Capybara/VisibilityMatcher:
|
|
90
|
-
Description: Checks for boolean visibility in Capybara finders.
|
|
91
|
-
Enabled: true
|
|
92
|
-
VersionAdded: '1.39'
|
|
93
|
-
VersionChanged: '2.0'
|
|
94
|
-
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/VisibilityMatcher
|
|
95
|
-
|
|
96
61
|
Capybara/RSpec:
|
|
97
62
|
Enabled: true
|
|
98
63
|
Include: *1
|
|
99
64
|
|
|
65
|
+
Capybara/RSpec/CurrentPathExpectation:
|
|
66
|
+
Description: Checks that no expectations are set on Capybara's `current_path`.
|
|
67
|
+
Enabled: pending
|
|
68
|
+
VersionAdded: '1.18'
|
|
69
|
+
VersionChanged: '2.23'
|
|
70
|
+
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/CurrentPathExpectation
|
|
71
|
+
|
|
72
|
+
Capybara/RSpec/HaveContent:
|
|
73
|
+
Description: Checks for usage of `have_content` and `have_no_content`.
|
|
74
|
+
Enabled: pending
|
|
75
|
+
VersionAdded: '2.23'
|
|
76
|
+
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/HaveContent
|
|
77
|
+
|
|
100
78
|
Capybara/RSpec/HaveSelector:
|
|
101
79
|
Description: Use `have_css` or `have_xpath` instead of `have_selector`.
|
|
102
80
|
Enabled: pending
|
|
@@ -104,6 +82,30 @@ Capybara/RSpec/HaveSelector:
|
|
|
104
82
|
VersionAdded: '2.19'
|
|
105
83
|
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/HaveSelector
|
|
106
84
|
|
|
85
|
+
Capybara/RSpec/MatchStyle:
|
|
86
|
+
Description: Checks for usage of deprecated style methods in RSpec matchers.
|
|
87
|
+
Enabled: pending
|
|
88
|
+
VersionAdded: '2.23'
|
|
89
|
+
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/MatchStyle
|
|
90
|
+
|
|
91
|
+
Capybara/RSpec/NegationMatcher:
|
|
92
|
+
Description: Enforces use of `have_no_*` or `not_to` for negated expectations.
|
|
93
|
+
Enabled: pending
|
|
94
|
+
VersionAdded: '2.14'
|
|
95
|
+
VersionChanged: '2.23'
|
|
96
|
+
EnforcedStyle: have_no
|
|
97
|
+
SupportedStyles:
|
|
98
|
+
- have_no
|
|
99
|
+
- not_to
|
|
100
|
+
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/NegationMatcher
|
|
101
|
+
|
|
102
|
+
Capybara/RSpec/NegationMatcherAfterVisit:
|
|
103
|
+
Description: Do not allow negative matchers to be used immediately after `visit`.
|
|
104
|
+
Enabled: pending
|
|
105
|
+
VersionAdded: '2.22'
|
|
106
|
+
VersionChanged: '2.23'
|
|
107
|
+
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/NegationMatcherAfterVisit
|
|
108
|
+
|
|
107
109
|
Capybara/RSpec/PredicateMatcher:
|
|
108
110
|
Description: Prefer using predicate matcher over using predicate method directly.
|
|
109
111
|
Enabled: pending
|
|
@@ -115,3 +117,17 @@ Capybara/RSpec/PredicateMatcher:
|
|
|
115
117
|
- explicit
|
|
116
118
|
VersionAdded: '2.19'
|
|
117
119
|
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/PredicateMatcher
|
|
120
|
+
|
|
121
|
+
Capybara/RSpec/SpecificMatcher:
|
|
122
|
+
Description: Checks for there is a more specific matcher offered by Capybara.
|
|
123
|
+
Enabled: pending
|
|
124
|
+
VersionAdded: '2.12'
|
|
125
|
+
VersionChanged: '2.23'
|
|
126
|
+
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/SpecificMatcher
|
|
127
|
+
|
|
128
|
+
Capybara/RSpec/VisibilityMatcher:
|
|
129
|
+
Description: Checks for boolean visibility in Capybara finders.
|
|
130
|
+
Enabled: pending
|
|
131
|
+
VersionAdded: '1.39'
|
|
132
|
+
VersionChanged: '2.23'
|
|
133
|
+
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/VisibilityMatcher
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Configuration of obsolete/deprecated cops used by `ConfigObsoletion`.
|
|
3
|
+
#
|
|
4
|
+
# See: https://docs.rubocop.org/rubocop/extensions.html#config-obsoletions
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
renamed:
|
|
8
|
+
Capybara/CurrentPathExpectation: Capybara/RSpec/CurrentPathExpectation
|
|
9
|
+
Capybara/NegationMatcher: Capybara/RSpec/NegationMatcher
|
|
10
|
+
Capybara/NegationMatcherAfterVisit: Capybara/RSpec/NegationMatcherAfterVisit
|
|
11
|
+
Capybara/SpecificMatcher: Capybara/RSpec/SpecificMatcher
|
|
12
|
+
Capybara/VisibilityMatcher: Capybara/RSpec/VisibilityMatcher
|
|
13
|
+
|
|
14
|
+
split:
|
|
15
|
+
Capybara/MatchStyle:
|
|
16
|
+
alternatives:
|
|
17
|
+
- Capybara/AssertStyle
|
|
18
|
+
- Capybara/RSpec/MatchStyle
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Capybara
|
|
6
|
+
# Checks for usage of deprecated assert style method.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# # bad
|
|
10
|
+
# page.find(:css, '#first').assert_style(display: 'block')
|
|
11
|
+
#
|
|
12
|
+
# # good
|
|
13
|
+
# page.find(:css, '#first').assert_matches_style(display: 'block')
|
|
14
|
+
#
|
|
15
|
+
class AssertStyle < ::RuboCop::Cop::Base
|
|
16
|
+
extend AutoCorrector
|
|
17
|
+
|
|
18
|
+
MSG = 'Use `assert_matches_style` instead of `assert_style`.'
|
|
19
|
+
RESTRICT_ON_SEND = %i[assert_style].freeze
|
|
20
|
+
|
|
21
|
+
def on_send(node)
|
|
22
|
+
method_node = node.loc.selector
|
|
23
|
+
add_offense(method_node) do |corrector|
|
|
24
|
+
corrector.replace(method_node, 'assert_matches_style')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -5,6 +5,13 @@ module RuboCop
|
|
|
5
5
|
module Capybara
|
|
6
6
|
# Enforces use of `first` instead of `all` with `first` or `[0]`.
|
|
7
7
|
#
|
|
8
|
+
# @safety
|
|
9
|
+
# This cop's autocorrection is unsafe because `all` returns a
|
|
10
|
+
# `Capybara::Result` (an enumerable collection), while `first`
|
|
11
|
+
# returns a single `Capybara::Node::Element`. Replacing `all`
|
|
12
|
+
# with `first` may break code that depends on the return value
|
|
13
|
+
# being a collection (e.g. calling `.each` on the result).
|
|
14
|
+
#
|
|
8
15
|
# @example
|
|
9
16
|
#
|
|
10
17
|
# # bad
|
|
@@ -46,6 +53,7 @@ module RuboCop
|
|
|
46
53
|
def on_all_first(node)
|
|
47
54
|
return unless (parent = node.parent)
|
|
48
55
|
return unless find_all_first?(parent)
|
|
56
|
+
return if part_of_logical_operator?(parent)
|
|
49
57
|
|
|
50
58
|
range = range_between(node.loc.selector.begin_pos,
|
|
51
59
|
parent.loc.selector.end_pos)
|
|
@@ -60,9 +68,11 @@ module RuboCop
|
|
|
60
68
|
include_match_first?(node) do |hash|
|
|
61
69
|
selector = ([node.first_argument.source] + replaced_hash(hash))
|
|
62
70
|
.join(', ')
|
|
63
|
-
|
|
71
|
+
range = range_between(node.loc.selector.begin_pos,
|
|
72
|
+
node.source_range.end_pos)
|
|
73
|
+
add_offense(range,
|
|
64
74
|
message: format(MSG, selector: selector)) do |corrector|
|
|
65
|
-
corrector.replace(
|
|
75
|
+
corrector.replace(range, "first(#{selector})")
|
|
66
76
|
end
|
|
67
77
|
end
|
|
68
78
|
end
|
|
@@ -72,6 +82,10 @@ module RuboCop
|
|
|
72
82
|
arg == 'match: :first'
|
|
73
83
|
end
|
|
74
84
|
end
|
|
85
|
+
|
|
86
|
+
def part_of_logical_operator?(node)
|
|
87
|
+
node.ancestors.any?(&:operator_keyword?)
|
|
88
|
+
end
|
|
75
89
|
end
|
|
76
90
|
end
|
|
77
91
|
end
|
|
@@ -83,6 +83,18 @@ module RuboCop
|
|
|
83
83
|
normalize = selector.gsub(/(\\[>,+~]|\(.*\))/, '')
|
|
84
84
|
normalize.match?(/[ >,+~]/)
|
|
85
85
|
end
|
|
86
|
+
|
|
87
|
+
# @param str [String]
|
|
88
|
+
# @param quote [String] the quote character used (' or ")
|
|
89
|
+
# @return [String]
|
|
90
|
+
# @example
|
|
91
|
+
# css_escape('foo.bar', "'") # => 'foo\.bar'
|
|
92
|
+
# css_escape('foo.bar', '"') # => 'foo\\.bar'
|
|
93
|
+
# css_escape('foo', "'") # => 'foo'
|
|
94
|
+
def css_escape(str, quote = "'")
|
|
95
|
+
escaped_dot = quote == '"' ? '\\\\\\\\.' : '\\\\.'
|
|
96
|
+
str.gsub('.', escaped_dot)
|
|
97
|
+
end
|
|
86
98
|
end
|
|
87
99
|
end
|
|
88
100
|
end
|
|
@@ -27,6 +27,7 @@ module RuboCop
|
|
|
27
27
|
# end
|
|
28
28
|
#
|
|
29
29
|
class RedundantWithinFind < ::RuboCop::Cop::Base
|
|
30
|
+
include CssSelector
|
|
30
31
|
extend AutoCorrector
|
|
31
32
|
MSG = 'Redundant `within %<method>s(...)` call detected.'
|
|
32
33
|
RESTRICT_ON_SEND = %i[within].freeze
|
|
@@ -53,13 +54,25 @@ module RuboCop
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def replaced(node)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
unless node.method?(:find_by_id)
|
|
58
|
+
return node.arguments.map(&:source).join(', ')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if node.first_argument.str_type?
|
|
62
|
+
build_escaped_selector(node.first_argument, node)
|
|
59
63
|
else
|
|
60
|
-
|
|
64
|
+
node.arguments.map(&:source).join(', ')
|
|
65
|
+
.sub(/\A(["'])/, '\1#')
|
|
61
66
|
end
|
|
62
67
|
end
|
|
68
|
+
|
|
69
|
+
def build_escaped_selector(first_arg, node)
|
|
70
|
+
quote = first_arg.source[0]
|
|
71
|
+
escaped_id = CssSelector.css_escape(first_arg.value, quote)
|
|
72
|
+
rest_args = node.arguments.drop(1).map(&:source)
|
|
73
|
+
|
|
74
|
+
["#{quote}##{escaped_id}#{quote}", *rest_args].join(', ')
|
|
75
|
+
end
|
|
63
76
|
end
|
|
64
77
|
end
|
|
65
78
|
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Capybara
|
|
6
|
+
module RSpec
|
|
7
|
+
# Checks that no expectations are set on Capybara's `current_path`.
|
|
8
|
+
#
|
|
9
|
+
# The
|
|
10
|
+
# https://www.rubydoc.info/github/teamcapybara/capybara/master/Capybara/RSpecMatchers#have_current_path-instance_method[`have_current_path` matcher]
|
|
11
|
+
# should be used on `page` to set expectations on Capybara's
|
|
12
|
+
# current path, since it uses
|
|
13
|
+
# https://github.com/teamcapybara/capybara/blob/master/README.md#asynchronous-javascript-ajax-and-friends[Capybara's waiting functionality]
|
|
14
|
+
# which ensures that preceding actions (like `click_link`) have
|
|
15
|
+
# completed.
|
|
16
|
+
#
|
|
17
|
+
# This cop does not support autocorrection in some cases.
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# # bad
|
|
21
|
+
# expect(current_path).to eq('/callback')
|
|
22
|
+
# expect(page.current_path).to eq('/callback')
|
|
23
|
+
#
|
|
24
|
+
# # good
|
|
25
|
+
# expect(page).to have_current_path('/callback', ignore_query: true)
|
|
26
|
+
#
|
|
27
|
+
# # bad (does not support autocorrection when `match` with a variable)
|
|
28
|
+
# expect(page).to match(variable)
|
|
29
|
+
#
|
|
30
|
+
class CurrentPathExpectation < ::RuboCop::Cop::Base
|
|
31
|
+
extend AutoCorrector
|
|
32
|
+
include RangeHelp
|
|
33
|
+
|
|
34
|
+
MSG = 'Do not set an RSpec expectation on `current_path` in ' \
|
|
35
|
+
'Capybara feature specs - instead, use the ' \
|
|
36
|
+
'`have_current_path` matcher on `page`'
|
|
37
|
+
|
|
38
|
+
RESTRICT_ON_SEND = %i[expect].freeze
|
|
39
|
+
|
|
40
|
+
# @!method expectation_set_on_current_path(node)
|
|
41
|
+
def_node_matcher :expectation_set_on_current_path, <<~PATTERN
|
|
42
|
+
(send nil? :expect (send {(send nil? :page) nil?} :current_path))
|
|
43
|
+
PATTERN
|
|
44
|
+
|
|
45
|
+
# Supported matchers: eq(...) / match(/regexp/) / match('regexp')
|
|
46
|
+
# @!method as_is_matcher(node)
|
|
47
|
+
def_node_matcher :as_is_matcher, <<~PATTERN
|
|
48
|
+
(send
|
|
49
|
+
#expectation_set_on_current_path ${:to :to_not :not_to}
|
|
50
|
+
${(send nil? :eq ...) (send nil? :match (regexp ...))})
|
|
51
|
+
PATTERN
|
|
52
|
+
|
|
53
|
+
# @!method regexp_node_matcher(node)
|
|
54
|
+
def_node_matcher :regexp_node_matcher, <<~PATTERN
|
|
55
|
+
(send
|
|
56
|
+
#expectation_set_on_current_path ${:to :to_not :not_to}
|
|
57
|
+
$(send nil? :match $any_str))
|
|
58
|
+
PATTERN
|
|
59
|
+
|
|
60
|
+
def self.autocorrect_incompatible_with
|
|
61
|
+
[Style::TrailingCommaInArguments]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def on_send(node)
|
|
65
|
+
expectation_set_on_current_path(node) do
|
|
66
|
+
add_offense(node.loc.selector) do |corrector|
|
|
67
|
+
next unless node.chained?
|
|
68
|
+
|
|
69
|
+
autocorrect(corrector, node)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def autocorrect(corrector, node)
|
|
77
|
+
as_is_matcher(node.parent) do |to_sym, matcher_node|
|
|
78
|
+
rewrite_expectation(corrector, node, to_sym, matcher_node)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
regexp_node_matcher(node.parent) do |to_sym, matcher_node, regexp|
|
|
82
|
+
rewrite_expectation(corrector, node, to_sym, matcher_node)
|
|
83
|
+
convert_regexp_node_to_literal(corrector, matcher_node, regexp)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def rewrite_expectation(corrector, node, to_symbol, matcher_node)
|
|
88
|
+
corrector.replace(node.first_argument, 'page')
|
|
89
|
+
corrector.replace(node.parent.loc.selector, 'to')
|
|
90
|
+
matcher_method = if to_symbol == :to
|
|
91
|
+
'have_current_path'
|
|
92
|
+
else
|
|
93
|
+
'have_no_current_path'
|
|
94
|
+
end
|
|
95
|
+
corrector.replace(matcher_node.loc.selector, matcher_method)
|
|
96
|
+
add_argument_parentheses(corrector, matcher_node.first_argument)
|
|
97
|
+
add_ignore_query_options(corrector, node, matcher_node)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def convert_regexp_node_to_literal(corrector, matcher_node,
|
|
101
|
+
regexp_node)
|
|
102
|
+
str_node = matcher_node.first_argument
|
|
103
|
+
regexp_expr = regexp_node_to_regexp_expr(regexp_node)
|
|
104
|
+
corrector.replace(str_node, regexp_expr)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def regexp_node_to_regexp_expr(regexp_node)
|
|
108
|
+
if regexp_node.xstr_type?
|
|
109
|
+
"/\#{`#{regexp_node.value.value}`}/"
|
|
110
|
+
else
|
|
111
|
+
Regexp.new(regexp_node.value).inspect
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def add_argument_parentheses(corrector, arg_node)
|
|
116
|
+
return unless method_call_with_no_parentheses?(arg_node)
|
|
117
|
+
|
|
118
|
+
first_argument_range = range_with_surrounding_space(
|
|
119
|
+
arg_node.first_argument.source_range, side: :left
|
|
120
|
+
)
|
|
121
|
+
corrector.insert_before(first_argument_range, '(')
|
|
122
|
+
corrector.insert_after(arg_node.last_argument, ')')
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def method_call_with_no_parentheses?(arg_node)
|
|
126
|
+
arg_node.send_type? && arg_node.arguments? &&
|
|
127
|
+
!arg_node.parenthesized?
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# `have_current_path` with no options will include the querystring
|
|
131
|
+
# while `page.current_path` does not.
|
|
132
|
+
# This ensures the option `ignore_query: true` is added
|
|
133
|
+
# except when `match` matcher.
|
|
134
|
+
def add_ignore_query_options(corrector, node, matcher_node)
|
|
135
|
+
return if matcher_node.method?(:match)
|
|
136
|
+
|
|
137
|
+
expectation_node = node.parent.last_argument
|
|
138
|
+
expectation_last_child = expectation_node.children.last
|
|
139
|
+
corrector.insert_after(expectation_last_child,
|
|
140
|
+
', ignore_query: true')
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Capybara
|
|
6
|
+
module RSpec
|
|
7
|
+
# Checks for usage of `have_content` and `have_no_content`.
|
|
8
|
+
#
|
|
9
|
+
# Capybara provides `have_text` and `have_no_text` matchers that are
|
|
10
|
+
# more concise and preferred over their aliases `have_content` and
|
|
11
|
+
# `have_no_content`.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# # bad
|
|
15
|
+
# expect(page).to have_content('capy')
|
|
16
|
+
# expect(page).to have_no_content('bara')
|
|
17
|
+
#
|
|
18
|
+
# # good
|
|
19
|
+
# expect(page).to have_text('capy')
|
|
20
|
+
# expect(page).to have_no_text('bara')
|
|
21
|
+
#
|
|
22
|
+
class HaveContent < ::RuboCop::Cop::Base
|
|
23
|
+
extend AutoCorrector
|
|
24
|
+
|
|
25
|
+
MSG = 'Prefer `%<good>s` over `%<bad>s`.'
|
|
26
|
+
RESTRICT_ON_SEND = %i[have_content have_no_content].freeze
|
|
27
|
+
PREFERRED_METHOD = {
|
|
28
|
+
'have_content' => 'have_text',
|
|
29
|
+
'have_no_content' => 'have_no_text'
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
def on_send(node)
|
|
33
|
+
method_node = node.loc.selector
|
|
34
|
+
add_offense(method_node,
|
|
35
|
+
message: message(method_node)) do |corrector|
|
|
36
|
+
corrector.replace(method_node,
|
|
37
|
+
PREFERRED_METHOD[method_node.source])
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
alias on_csend on_send
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def message(node)
|
|
45
|
+
format(MSG, good: PREFERRED_METHOD[node.source], bad: node.source)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -52,9 +52,10 @@ module RuboCop
|
|
|
52
52
|
|
|
53
53
|
def on_select_with_type(node, type)
|
|
54
54
|
return unless SELECTORS.include?(type.value)
|
|
55
|
+
return unless (locator = node.arguments[1])
|
|
55
56
|
|
|
56
57
|
add_offense(node, message: message_typed(type)) do |corrector|
|
|
57
|
-
corrector.remove(deletion_range(type,
|
|
58
|
+
corrector.remove(deletion_range(type, locator))
|
|
58
59
|
corrector.replace(node.loc.selector, "have_#{type.value}")
|
|
59
60
|
end
|
|
60
61
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Capybara
|
|
6
|
+
module RSpec
|
|
7
|
+
# Checks for usage of deprecated style methods in RSpec matchers.
|
|
8
|
+
#
|
|
9
|
+
# @example when using `has_style?`
|
|
10
|
+
# # bad
|
|
11
|
+
# expect(page.find(:css, 'first')
|
|
12
|
+
# .has_style?(display: 'block')).to be true
|
|
13
|
+
#
|
|
14
|
+
# # good
|
|
15
|
+
# expect(page.find(:css, 'first')
|
|
16
|
+
# .matches_style?(display: 'block')).to be true
|
|
17
|
+
#
|
|
18
|
+
# @example when using `have_style`
|
|
19
|
+
# # bad
|
|
20
|
+
# expect(page).to have_style(display: 'block')
|
|
21
|
+
#
|
|
22
|
+
# # good
|
|
23
|
+
# expect(page).to match_style(display: 'block')
|
|
24
|
+
#
|
|
25
|
+
class MatchStyle < ::RuboCop::Cop::Base
|
|
26
|
+
extend AutoCorrector
|
|
27
|
+
|
|
28
|
+
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
|
29
|
+
RESTRICT_ON_SEND = %i[has_style? have_style].freeze
|
|
30
|
+
PREFERRED_METHOD = {
|
|
31
|
+
'has_style?' => 'matches_style?',
|
|
32
|
+
'have_style' => 'match_style'
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
def on_send(node)
|
|
36
|
+
method_node = node.loc.selector
|
|
37
|
+
preferred = PREFERRED_METHOD[method_node.source]
|
|
38
|
+
add_offense(method_node,
|
|
39
|
+
message: message(preferred, method_node)) do |corrector|
|
|
40
|
+
corrector.replace(method_node, preferred)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def message(preferred, method_node)
|
|
47
|
+
format(MSG, good: preferred, bad: method_node.source)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|