rubocop-rspec 2.17.0 → 2.18.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 +12 -0
- data/config/obsoletion.yml +9 -0
- data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +29 -115
- data/lib/rubocop/cop/rspec/capybara/match_style.rb +28 -50
- data/lib/rubocop/cop/rspec/capybara/negation_matcher.rb +23 -96
- data/lib/rubocop/cop/rspec/capybara/specific_actions.rb +19 -75
- data/lib/rubocop/cop/rspec/capybara/specific_finders.rb +14 -83
- data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +25 -69
- data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +26 -63
- data/lib/rubocop/cop/rspec/context_method.rb +5 -1
- data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +1 -1
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +35 -1
- data/lib/rubocop/rspec/config_formatter.rb +10 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop-rspec.rb +1 -2
- metadata +16 -4
- data/lib/rubocop/cop/rspec/mixin/capybara_help.rb +0 -80
- data/lib/rubocop/cop/rspec/mixin/css_selector.rb +0 -146
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca669591a3d96ff467934951daa0d1babb0dfbab10a51ee1b170827ee297838e
|
4
|
+
data.tar.gz: 0b72afb14344b5b5913f912c5c27d061a8eaee6a8c3c94dfa176cb83c36f0e84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77179cdfde546ab60481f00538ec4e18cb9db86c2a2dd7bdbcb834f6ee7ea7f7399bdf487d753df6225500783db77174c87c9f2d314a2af4870a0c71631ced5d
|
7
|
+
data.tar.gz: a5181f81b66daba503eded433c3407d4aa9be3f866310bdefc83b7af0f3783783d0f18fa1dbde1847deee267c112ba4b2c566b484369c59c67a381a53adf5421
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,18 @@
|
|
2
2
|
|
3
3
|
## Master (Unreleased)
|
4
4
|
|
5
|
+
## 2.18.0 (2023-01-16)
|
6
|
+
|
7
|
+
- Extract Capybara cops to a separate repository. ([@pirj])
|
8
|
+
|
9
|
+
## 2.17.1 (2023-01-16)
|
10
|
+
|
11
|
+
- Fix a false negative for `RSpec/Pending` when using skipped in metadata is multiline string. ([@ydah])
|
12
|
+
- Fix a false positive for `RSpec/NoExpectationExample` when using skipped in metadata is multiline string. ([@ydah])
|
13
|
+
- Fix a false positive for `RSpec/ContextMethod` when multi-line context with `#` at the beginning. ([@ydah])
|
14
|
+
- Fix an incorrect autocorrect for `RSpec/PredicateMatcher` when multiline expect and predicate method with heredoc. ([@ydah])
|
15
|
+
- Fix a false positive for `RSpec/PredicateMatcher` when `include` with multiple argument. ([@ydah])
|
16
|
+
|
5
17
|
## 2.17.0 (2023-01-13)
|
6
18
|
|
7
19
|
- Fix a false positive for `RSpec/PendingWithoutReason` when pending/skip is argument of methods. ([@ydah])
|
data/config/obsoletion.yml
CHANGED
@@ -12,3 +12,12 @@ changed_parameters:
|
|
12
12
|
parameters: IgnoredPatterns
|
13
13
|
alternative: AllowedPatterns
|
14
14
|
severity: warning
|
15
|
+
|
16
|
+
renamed:
|
17
|
+
RSpec/Capybara/CurrentPathExpectation: Capybara/CurrentPathExpectation
|
18
|
+
RSpec/Capybara/MatchStyle: Capybara/MatchStyle
|
19
|
+
RSpec/Capybara/NegationMatcher: Capybara/NegationMatcher
|
20
|
+
RSpec/Capybara/SpecificActions: Capybara/SpecificActions
|
21
|
+
RSpec/Capybara/SpecificFinders: Capybara/SpecificFinders
|
22
|
+
RSpec/Capybara/SpecificMatcher: Capybara/SpecificMatcher
|
23
|
+
RSpec/Capybara/VisibilityMatcher: Capybara/VisibilityMatcher
|
@@ -4,121 +4,35 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
6
|
module Capybara
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# #
|
21
|
-
#
|
22
|
-
#
|
23
|
-
# #
|
24
|
-
#
|
25
|
-
#
|
26
|
-
# #
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# #
|
30
|
-
#
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
'Capybara feature specs - instead, use the ' \
|
37
|
-
'`have_current_path` matcher on `page`'
|
38
|
-
|
39
|
-
RESTRICT_ON_SEND = %i[expect].freeze
|
40
|
-
|
41
|
-
# @!method expectation_set_on_current_path(node)
|
42
|
-
def_node_matcher :expectation_set_on_current_path, <<-PATTERN
|
43
|
-
(send nil? :expect (send {(send nil? :page) nil?} :current_path))
|
44
|
-
PATTERN
|
45
|
-
|
46
|
-
# Supported matchers: eq(...) / match(/regexp/) / match('regexp')
|
47
|
-
# @!method as_is_matcher(node)
|
48
|
-
def_node_matcher :as_is_matcher, <<-PATTERN
|
49
|
-
(send
|
50
|
-
#expectation_set_on_current_path ${:to :to_not :not_to}
|
51
|
-
${(send nil? :eq ...) (send nil? :match (regexp ...))})
|
52
|
-
PATTERN
|
53
|
-
|
54
|
-
# @!method regexp_str_matcher(node)
|
55
|
-
def_node_matcher :regexp_str_matcher, <<-PATTERN
|
56
|
-
(send
|
57
|
-
#expectation_set_on_current_path ${:to :to_not :not_to}
|
58
|
-
$(send nil? :match (str $_)))
|
59
|
-
PATTERN
|
60
|
-
|
61
|
-
def self.autocorrect_incompatible_with
|
62
|
-
[Style::TrailingCommaInArguments]
|
63
|
-
end
|
64
|
-
|
65
|
-
def on_send(node)
|
66
|
-
expectation_set_on_current_path(node) do
|
67
|
-
add_offense(node.loc.selector) do |corrector|
|
68
|
-
next unless node.chained?
|
69
|
-
|
70
|
-
autocorrect(corrector, node)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def autocorrect(corrector, node)
|
78
|
-
as_is_matcher(node.parent) do |to_sym, matcher_node|
|
79
|
-
rewrite_expectation(corrector, node, to_sym, matcher_node)
|
80
|
-
end
|
81
|
-
|
82
|
-
regexp_str_matcher(node.parent) do |to_sym, matcher_node, regexp|
|
83
|
-
rewrite_expectation(corrector, node, to_sym, matcher_node)
|
84
|
-
convert_regexp_str_to_literal(corrector, matcher_node, regexp)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def rewrite_expectation(corrector, node, to_symbol, matcher_node)
|
89
|
-
current_path_node = node.first_argument
|
90
|
-
corrector.replace(current_path_node, 'page')
|
91
|
-
corrector.replace(node.parent.loc.selector, 'to')
|
92
|
-
matcher_method = if to_symbol == :to
|
93
|
-
'have_current_path'
|
94
|
-
else
|
95
|
-
'have_no_current_path'
|
96
|
-
end
|
97
|
-
corrector.replace(matcher_node.loc.selector, matcher_method)
|
98
|
-
add_ignore_query_options(corrector, node)
|
99
|
-
end
|
100
|
-
|
101
|
-
def convert_regexp_str_to_literal(corrector, matcher_node, regexp_str)
|
102
|
-
str_node = matcher_node.first_argument
|
103
|
-
regexp_expr = Regexp.new(regexp_str).inspect
|
104
|
-
corrector.replace(str_node, regexp_expr)
|
105
|
-
end
|
106
|
-
|
107
|
-
# `have_current_path` with no options will include the querystring
|
108
|
-
# while `page.current_path` does not.
|
109
|
-
# This ensures the option `ignore_query: true` is added
|
110
|
-
# except when the expectation is a regexp or string
|
111
|
-
def add_ignore_query_options(corrector, node)
|
112
|
-
expectation_node = node.parent.last_argument
|
113
|
-
expectation_last_child = expectation_node.children.last
|
114
|
-
return if %i[regexp str].include?(expectation_last_child.type)
|
115
|
-
|
116
|
-
corrector.insert_after(
|
117
|
-
expectation_last_child,
|
118
|
-
', ignore_query: true'
|
119
|
-
)
|
120
|
-
end
|
121
|
-
end
|
7
|
+
# @!parse
|
8
|
+
# # Checks that no expectations are set on Capybara's `current_path`.
|
9
|
+
# #
|
10
|
+
# # The
|
11
|
+
# # https://www.rubydoc.info/github/teamcapybara/capybara/master/Capybara/RSpecMatchers#have_current_path-instance_method[`have_current_path` matcher]
|
12
|
+
# # should be used on `page` to set expectations on Capybara's
|
13
|
+
# # current path, since it uses
|
14
|
+
# # https://github.com/teamcapybara/capybara/blob/master/README.md#asynchronous-javascript-ajax-and-friends[Capybara's waiting functionality]
|
15
|
+
# # which ensures that preceding actions (like `click_link`) have
|
16
|
+
# # completed.
|
17
|
+
# #
|
18
|
+
# # This cop does not support autocorrection in some cases.
|
19
|
+
# #
|
20
|
+
# # @example
|
21
|
+
# # # bad
|
22
|
+
# # expect(current_path).to eq('/callback')
|
23
|
+
# #
|
24
|
+
# # # good
|
25
|
+
# # expect(page).to have_current_path('/callback')
|
26
|
+
# #
|
27
|
+
# # # bad (does not support autocorrection)
|
28
|
+
# # expect(page.current_path).to match(variable)
|
29
|
+
# #
|
30
|
+
# # # good
|
31
|
+
# # expect(page).to have_current_path('/callback')
|
32
|
+
# #
|
33
|
+
# class CurrentPathExpectation < ::RuboCop::Cop::Base; end
|
34
|
+
CurrentPathExpectation =
|
35
|
+
::RuboCop::Cop::Capybara::CurrentPathExpectation
|
122
36
|
end
|
123
37
|
end
|
124
38
|
end
|
@@ -4,56 +4,34 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
6
|
module Capybara
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# #
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# #
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# #
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# #
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
# #
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# #
|
30
|
-
#
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
36
|
-
RESTRICT_ON_SEND = %i[assert_style has_style? have_style].freeze
|
37
|
-
PREFERRED_METHOD = {
|
38
|
-
'assert_style' => 'assert_matches_style',
|
39
|
-
'has_style?' => 'matches_style?',
|
40
|
-
'have_style' => 'match_style'
|
41
|
-
}.freeze
|
42
|
-
|
43
|
-
def on_send(node)
|
44
|
-
method_node = node.loc.selector
|
45
|
-
add_offense(method_node) do |corrector|
|
46
|
-
corrector.replace(method_node,
|
47
|
-
PREFERRED_METHOD[method_node.source])
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def message(node)
|
54
|
-
format(MSG, good: PREFERRED_METHOD[node.source], bad: node.source)
|
55
|
-
end
|
56
|
-
end
|
7
|
+
# @!parse
|
8
|
+
# # Checks for usage of deprecated style methods.
|
9
|
+
# #
|
10
|
+
# # @example when using `assert_style`
|
11
|
+
# # # bad
|
12
|
+
# # page.find(:css, '#first').assert_style(display: 'block')
|
13
|
+
# #
|
14
|
+
# # # good
|
15
|
+
# # page.find(:css, '#first').assert_matches_style(display: 'block')
|
16
|
+
# #
|
17
|
+
# # @example when using `has_style?`
|
18
|
+
# # # bad
|
19
|
+
# # expect(page.find(:css, 'first')
|
20
|
+
# # .has_style?(display: 'block')).to be true
|
21
|
+
# #
|
22
|
+
# # # good
|
23
|
+
# # expect(page.find(:css, 'first')
|
24
|
+
# # .matches_style?(display: 'block')).to be true
|
25
|
+
# #
|
26
|
+
# # @example when using `have_style`
|
27
|
+
# # # bad
|
28
|
+
# # expect(page).to have_style(display: 'block')
|
29
|
+
# #
|
30
|
+
# # # good
|
31
|
+
# # expect(page).to match_style(display: 'block')
|
32
|
+
# #
|
33
|
+
# class MatchStyle < ::RuboCop::Cop::Base; end
|
34
|
+
MatchStyle = ::RuboCop::Cop::Capybara::MatchStyle
|
57
35
|
end
|
58
36
|
end
|
59
37
|
end
|
@@ -4,102 +4,29 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
6
|
module Capybara
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# #
|
11
|
-
#
|
12
|
-
# expect(page).to
|
13
|
-
#
|
14
|
-
# #
|
15
|
-
#
|
16
|
-
# expect(page).not_to
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# #
|
20
|
-
#
|
21
|
-
# expect(page).not_to
|
22
|
-
#
|
23
|
-
# #
|
24
|
-
#
|
25
|
-
# expect(page).to
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
|
32
|
-
CAPYBARA_MATCHERS = %w[
|
33
|
-
selector css xpath text title current_path link button
|
34
|
-
field checked_field unchecked_field select table
|
35
|
-
sibling ancestor
|
36
|
-
].freeze
|
37
|
-
POSITIVE_MATCHERS =
|
38
|
-
Set.new(CAPYBARA_MATCHERS) { |element| :"have_#{element}" }.freeze
|
39
|
-
NEGATIVE_MATCHERS =
|
40
|
-
Set.new(CAPYBARA_MATCHERS) { |element| :"have_no_#{element}" }
|
41
|
-
.freeze
|
42
|
-
RESTRICT_ON_SEND = (POSITIVE_MATCHERS + NEGATIVE_MATCHERS).freeze
|
43
|
-
|
44
|
-
# @!method not_to?(node)
|
45
|
-
def_node_matcher :not_to?, <<~PATTERN
|
46
|
-
(send ... :not_to
|
47
|
-
(send nil? %POSITIVE_MATCHERS ...))
|
48
|
-
PATTERN
|
49
|
-
|
50
|
-
# @!method have_no?(node)
|
51
|
-
def_node_matcher :have_no?, <<~PATTERN
|
52
|
-
(send ... :to
|
53
|
-
(send nil? %NEGATIVE_MATCHERS ...))
|
54
|
-
PATTERN
|
55
|
-
|
56
|
-
def on_send(node)
|
57
|
-
return unless offense?(node.parent)
|
58
|
-
|
59
|
-
matcher = node.method_name.to_s
|
60
|
-
add_offense(offense_range(node),
|
61
|
-
message: message(matcher)) do |corrector|
|
62
|
-
corrector.replace(node.parent.loc.selector, replaced_runner)
|
63
|
-
corrector.replace(node.loc.selector,
|
64
|
-
replaced_matcher(matcher))
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def offense?(node)
|
71
|
-
(style == :have_no && not_to?(node)) ||
|
72
|
-
(style == :not_to && have_no?(node))
|
73
|
-
end
|
74
|
-
|
75
|
-
def offense_range(node)
|
76
|
-
node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
|
77
|
-
end
|
78
|
-
|
79
|
-
def message(matcher)
|
80
|
-
format(MSG,
|
81
|
-
runner: replaced_runner,
|
82
|
-
matcher: replaced_matcher(matcher))
|
83
|
-
end
|
84
|
-
|
85
|
-
def replaced_runner
|
86
|
-
case style
|
87
|
-
when :have_no
|
88
|
-
'to'
|
89
|
-
when :not_to
|
90
|
-
'not_to'
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def replaced_matcher(matcher)
|
95
|
-
case style
|
96
|
-
when :have_no
|
97
|
-
matcher.sub('have_', 'have_no_')
|
98
|
-
when :not_to
|
99
|
-
matcher.sub('have_no_', 'have_')
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
7
|
+
# @!parse
|
8
|
+
# # Enforces use of `have_no_*` or `not_to` for negated expectations.
|
9
|
+
# #
|
10
|
+
# # @example EnforcedStyle: not_to (default)
|
11
|
+
# # # bad
|
12
|
+
# # expect(page).to have_no_selector
|
13
|
+
# # expect(page).to have_no_css('a')
|
14
|
+
# #
|
15
|
+
# # # good
|
16
|
+
# # expect(page).not_to have_selector
|
17
|
+
# # expect(page).not_to have_css('a')
|
18
|
+
# #
|
19
|
+
# # @example EnforcedStyle: have_no
|
20
|
+
# # # bad
|
21
|
+
# # expect(page).not_to have_selector
|
22
|
+
# # expect(page).not_to have_css('a')
|
23
|
+
# #
|
24
|
+
# # # good
|
25
|
+
# # expect(page).to have_no_selector
|
26
|
+
# # expect(page).to have_no_css('a')
|
27
|
+
# #
|
28
|
+
# class NegationMatcher < ::RuboCop::Cop::Base; end
|
29
|
+
NegationMatcher = ::RuboCop::Cop::Capybara::NegationMatcher
|
103
30
|
end
|
104
31
|
end
|
105
32
|
end
|
@@ -4,81 +4,25 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
6
|
module Capybara
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# #
|
12
|
-
#
|
13
|
-
# find('
|
14
|
-
# find('
|
15
|
-
# find('
|
16
|
-
#
|
17
|
-
# #
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
SPECIFIC_ACTION = {
|
27
|
-
'button' => 'button',
|
28
|
-
'a' => 'link'
|
29
|
-
}.freeze
|
30
|
-
|
31
|
-
# @!method click_on_selector(node)
|
32
|
-
def_node_matcher :click_on_selector, <<-PATTERN
|
33
|
-
(send _ :find (str $_) ...)
|
34
|
-
PATTERN
|
35
|
-
|
36
|
-
def on_send(node)
|
37
|
-
click_on_selector(node.receiver) do |arg|
|
38
|
-
next unless supported_selector?(arg)
|
39
|
-
# Always check the last selector in the case of multiple selectors
|
40
|
-
# separated by whitespace.
|
41
|
-
# because the `.click` is executed on the element to
|
42
|
-
# which the last selector points.
|
43
|
-
next unless (selector = last_selector(arg))
|
44
|
-
next unless (action = specific_action(selector))
|
45
|
-
next unless CapybaraHelp.specific_option?(node.receiver, arg,
|
46
|
-
action)
|
47
|
-
next unless CapybaraHelp.specific_pseudo_classes?(arg)
|
48
|
-
|
49
|
-
range = offense_range(node, node.receiver)
|
50
|
-
add_offense(range, message: message(action, selector))
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def specific_action(selector)
|
57
|
-
SPECIFIC_ACTION[last_selector(selector)]
|
58
|
-
end
|
59
|
-
|
60
|
-
def supported_selector?(selector)
|
61
|
-
!selector.match?(/[>,+~]/)
|
62
|
-
end
|
63
|
-
|
64
|
-
def last_selector(arg)
|
65
|
-
arg.split.last[/^\w+/, 0]
|
66
|
-
end
|
67
|
-
|
68
|
-
def offense_range(node, receiver)
|
69
|
-
receiver.loc.selector.with(end_pos: node.loc.expression.end_pos)
|
70
|
-
end
|
71
|
-
|
72
|
-
def message(action, selector)
|
73
|
-
format(MSG,
|
74
|
-
good_action: good_action(action),
|
75
|
-
selector: selector)
|
76
|
-
end
|
77
|
-
|
78
|
-
def good_action(action)
|
79
|
-
"click_#{action}"
|
80
|
-
end
|
81
|
-
end
|
7
|
+
# @!parse
|
8
|
+
# # Checks for there is a more specific actions offered by Capybara.
|
9
|
+
# #
|
10
|
+
# # @example
|
11
|
+
# #
|
12
|
+
# # # bad
|
13
|
+
# # find('a').click
|
14
|
+
# # find('button.cls').click
|
15
|
+
# # find('a', exact_text: 'foo').click
|
16
|
+
# # find('div button').click
|
17
|
+
# #
|
18
|
+
# # # good
|
19
|
+
# # click_link
|
20
|
+
# # click_button(class: 'cls')
|
21
|
+
# # click_link(exact_text: 'foo')
|
22
|
+
# # find('div').click_button
|
23
|
+
# #
|
24
|
+
# class SpecificActions < ::RuboCop::Cop::Base; end
|
25
|
+
SpecificActions = ::RuboCop::Cop::Capybara::SpecificActions
|
82
26
|
end
|
83
27
|
end
|
84
28
|
end
|
@@ -4,89 +4,20 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
6
|
module Capybara
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# #
|
11
|
-
#
|
12
|
-
# find('
|
13
|
-
#
|
14
|
-
# #
|
15
|
-
#
|
16
|
-
# find_by_id('some-id'
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
include RangeHelp
|
22
|
-
|
23
|
-
MSG = 'Prefer `find_by` over `find`.'
|
24
|
-
RESTRICT_ON_SEND = %i[find].freeze
|
25
|
-
|
26
|
-
# @!method find_argument(node)
|
27
|
-
def_node_matcher :find_argument, <<~PATTERN
|
28
|
-
(send _ :find (str $_) ...)
|
29
|
-
PATTERN
|
30
|
-
|
31
|
-
def on_send(node)
|
32
|
-
find_argument(node) do |arg|
|
33
|
-
next if CssSelector.multiple_selectors?(arg)
|
34
|
-
|
35
|
-
on_attr(node, arg) if attribute?(arg)
|
36
|
-
on_id(node, arg) if CssSelector.id?(arg)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def on_attr(node, arg)
|
43
|
-
return unless (id = CssSelector.attributes(arg)['id'])
|
44
|
-
|
45
|
-
register_offense(node, replaced_arguments(arg, id))
|
46
|
-
end
|
47
|
-
|
48
|
-
def on_id(node, arg)
|
49
|
-
register_offense(node, "'#{arg.to_s.delete('#')}'")
|
50
|
-
end
|
51
|
-
|
52
|
-
def attribute?(arg)
|
53
|
-
CssSelector.attribute?(arg) &&
|
54
|
-
CssSelector.common_attributes?(arg)
|
55
|
-
end
|
56
|
-
|
57
|
-
def register_offense(node, arg_replacement)
|
58
|
-
add_offense(offense_range(node)) do |corrector|
|
59
|
-
corrector.replace(node.loc.selector, 'find_by_id')
|
60
|
-
corrector.replace(node.first_argument.loc.expression,
|
61
|
-
arg_replacement)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def replaced_arguments(arg, id)
|
66
|
-
options = to_options(CssSelector.attributes(arg))
|
67
|
-
options.empty? ? id : "#{id}, #{options}"
|
68
|
-
end
|
69
|
-
|
70
|
-
def to_options(attrs)
|
71
|
-
attrs.each.map do |key, value|
|
72
|
-
next if key == 'id'
|
73
|
-
|
74
|
-
"#{key}: #{value}"
|
75
|
-
end.compact.join(', ')
|
76
|
-
end
|
77
|
-
|
78
|
-
def offense_range(node)
|
79
|
-
range_between(node.loc.selector.begin_pos, end_pos(node))
|
80
|
-
end
|
81
|
-
|
82
|
-
def end_pos(node)
|
83
|
-
if node.loc.end
|
84
|
-
node.loc.end.end_pos
|
85
|
-
else
|
86
|
-
node.loc.expression.end_pos
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
7
|
+
# @!parse
|
8
|
+
# # Checks if there is a more specific finder offered by Capybara.
|
9
|
+
# #
|
10
|
+
# # @example
|
11
|
+
# # # bad
|
12
|
+
# # find('#some-id')
|
13
|
+
# # find('[visible][id=some-id]')
|
14
|
+
# #
|
15
|
+
# # # good
|
16
|
+
# # find_by_id('some-id')
|
17
|
+
# # find_by_id('some-id', visible: true)
|
18
|
+
# #
|
19
|
+
# class SpecificFinders < ::RuboCop::Cop::Base; end
|
20
|
+
SpecificFinders = ::RuboCop::Cop::Capybara::SpecificFinders
|
90
21
|
end
|
91
22
|
end
|
92
23
|
end
|
@@ -4,75 +4,31 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
6
|
module Capybara
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# #
|
12
|
-
#
|
13
|
-
# expect(page).to
|
14
|
-
# expect(page).to
|
15
|
-
# expect(page).to
|
16
|
-
# expect(page).to
|
17
|
-
# expect(page).to have_css('
|
18
|
-
# expect(page).to have_css('
|
19
|
-
#
|
20
|
-
# #
|
21
|
-
#
|
22
|
-
# expect(page).to
|
23
|
-
# expect(page).to
|
24
|
-
# expect(page).to
|
25
|
-
# expect(page).to
|
26
|
-
# expect(page).to
|
27
|
-
# expect(page).to
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
have_no_css].freeze
|
33
|
-
SPECIFIC_MATCHER = {
|
34
|
-
'button' => 'button',
|
35
|
-
'a' => 'link',
|
36
|
-
'table' => 'table',
|
37
|
-
'select' => 'select',
|
38
|
-
'input' => 'field'
|
39
|
-
}.freeze
|
40
|
-
|
41
|
-
# @!method first_argument(node)
|
42
|
-
def_node_matcher :first_argument, <<-PATTERN
|
43
|
-
(send nil? _ (str $_) ... )
|
44
|
-
PATTERN
|
45
|
-
|
46
|
-
def on_send(node)
|
47
|
-
first_argument(node) do |arg|
|
48
|
-
next unless (matcher = specific_matcher(arg))
|
49
|
-
next if CssSelector.multiple_selectors?(arg)
|
50
|
-
next unless CapybaraHelp.specific_option?(node, arg, matcher)
|
51
|
-
next unless CapybaraHelp.specific_pseudo_classes?(arg)
|
52
|
-
|
53
|
-
add_offense(node, message: message(node, matcher))
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def specific_matcher(arg)
|
60
|
-
splitted_arg = arg[/^\w+/, 0]
|
61
|
-
SPECIFIC_MATCHER[splitted_arg]
|
62
|
-
end
|
63
|
-
|
64
|
-
def message(node, matcher)
|
65
|
-
format(MSG,
|
66
|
-
good_matcher: good_matcher(node, matcher),
|
67
|
-
bad_matcher: node.method_name)
|
68
|
-
end
|
69
|
-
|
70
|
-
def good_matcher(node, matcher)
|
71
|
-
node.method_name
|
72
|
-
.to_s
|
73
|
-
.gsub(/selector|css/, matcher.to_s)
|
74
|
-
end
|
75
|
-
end
|
7
|
+
# @!parse
|
8
|
+
# # Checks for there is a more specific matcher offered by Capybara.
|
9
|
+
# #
|
10
|
+
# # @example
|
11
|
+
# #
|
12
|
+
# # # bad
|
13
|
+
# # expect(page).to have_selector('button')
|
14
|
+
# # expect(page).to have_no_selector('button.cls')
|
15
|
+
# # expect(page).to have_css('button')
|
16
|
+
# # expect(page).to have_no_css('a.cls', href: 'http://example.com')
|
17
|
+
# # expect(page).to have_css('table.cls')
|
18
|
+
# # expect(page).to have_css('select')
|
19
|
+
# # expect(page).to have_css('input', exact_text: 'foo')
|
20
|
+
# #
|
21
|
+
# # # good
|
22
|
+
# # expect(page).to have_button
|
23
|
+
# # expect(page).to have_no_button(class: 'cls')
|
24
|
+
# # expect(page).to have_button
|
25
|
+
# # expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
|
26
|
+
# # expect(page).to have_table(class: 'cls')
|
27
|
+
# # expect(page).to have_select
|
28
|
+
# # expect(page).to have_field('foo')
|
29
|
+
# #
|
30
|
+
# class SpecificMatcher < ::RuboCop::Cop::Base; end
|
31
|
+
SpecificMatcher = ::RuboCop::Cop::Capybara::SpecificMatcher
|
76
32
|
end
|
77
33
|
end
|
78
34
|
end
|
@@ -4,69 +4,32 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
6
|
module Capybara
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# #
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# expect(page).to
|
23
|
-
#
|
24
|
-
# #
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# expect(page).to
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
button
|
34
|
-
checked_field
|
35
|
-
css
|
36
|
-
field
|
37
|
-
link
|
38
|
-
select
|
39
|
-
selector
|
40
|
-
table
|
41
|
-
unchecked_field
|
42
|
-
xpath
|
43
|
-
].flat_map do |element|
|
44
|
-
["have_#{element}".to_sym, "have_no_#{element}".to_sym]
|
45
|
-
end
|
46
|
-
|
47
|
-
RESTRICT_ON_SEND = CAPYBARA_MATCHER_METHODS
|
48
|
-
|
49
|
-
# @!method visible_true?(node)
|
50
|
-
def_node_matcher :visible_true?, <<~PATTERN
|
51
|
-
(send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) true) ...>))
|
52
|
-
PATTERN
|
53
|
-
|
54
|
-
# @!method visible_false?(node)
|
55
|
-
def_node_matcher :visible_false?, <<~PATTERN
|
56
|
-
(send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) false) ...>))
|
57
|
-
PATTERN
|
58
|
-
|
59
|
-
def on_send(node)
|
60
|
-
visible_false?(node) { |arg| add_offense(arg, message: MSG_FALSE) }
|
61
|
-
visible_true?(node) { |arg| add_offense(arg, message: MSG_TRUE) }
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def capybara_matcher?(method_name)
|
67
|
-
CAPYBARA_MATCHER_METHODS.include? method_name
|
68
|
-
end
|
69
|
-
end
|
7
|
+
# @!parse
|
8
|
+
# # Checks for boolean visibility in Capybara finders.
|
9
|
+
# #
|
10
|
+
# # Capybara lets you find elements that match a certain visibility
|
11
|
+
# # using the `:visible` option. `:visible` accepts both boolean and
|
12
|
+
# # symbols as values, however using booleans can have unwanted
|
13
|
+
# # effects. `visible: false` does not find just invisible elements,
|
14
|
+
# # but both visible and invisible elements. For expressiveness and
|
15
|
+
# # clarity, use one of the # symbol values, `:all`, `:hidden` or
|
16
|
+
# # `:visible`.
|
17
|
+
# # Read more in
|
18
|
+
# # https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all[the documentation].
|
19
|
+
# #
|
20
|
+
# # @example
|
21
|
+
# # # bad
|
22
|
+
# # expect(page).to have_selector('.foo', visible: false)
|
23
|
+
# # expect(page).to have_css('.foo', visible: true)
|
24
|
+
# # expect(page).to have_link('my link', visible: false)
|
25
|
+
# #
|
26
|
+
# # # good
|
27
|
+
# # expect(page).to have_selector('.foo', visible: :visible)
|
28
|
+
# # expect(page).to have_css('.foo', visible: :all)
|
29
|
+
# # expect(page).to have_link('my link', visible: :hidden)
|
30
|
+
# #
|
31
|
+
# class VisibilityMatcher < ::RuboCop::Cop::Base; end
|
32
|
+
VisibilityMatcher = ::RuboCop::Cop::Capybara::VisibilityMatcher
|
70
33
|
end
|
71
34
|
end
|
72
35
|
end
|
@@ -31,7 +31,11 @@ module RuboCop
|
|
31
31
|
|
32
32
|
# @!method context_method(node)
|
33
33
|
def_node_matcher :context_method, <<-PATTERN
|
34
|
-
(block
|
34
|
+
(block
|
35
|
+
(send #rspec? :context
|
36
|
+
${(str #method_name?) (dstr (str #method_name?) ...)}
|
37
|
+
...)
|
38
|
+
...)
|
35
39
|
PATTERN
|
36
40
|
|
37
41
|
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
@@ -11,7 +11,7 @@ module RuboCop
|
|
11
11
|
def_node_matcher :skipped_in_metadata?, <<-PATTERN
|
12
12
|
{
|
13
13
|
(send _ _ <#skip_or_pending? ...>)
|
14
|
-
(send _ _ ... (hash <(pair #skip_or_pending? { true str }) ...>))
|
14
|
+
(send _ _ ... (hash <(pair #skip_or_pending? { true str dstr }) ...>))
|
15
15
|
}
|
16
16
|
PATTERN
|
17
17
|
|
@@ -118,7 +118,7 @@ module RuboCop
|
|
118
118
|
end
|
119
119
|
|
120
120
|
# A helper for `explicit` style
|
121
|
-
module ExplicitHelper
|
121
|
+
module ExplicitHelper # rubocop:disable Metrics/ModuleLength
|
122
122
|
include RuboCop::RSpec::Language
|
123
123
|
extend NodePattern::Macros
|
124
124
|
|
@@ -149,12 +149,35 @@ module RuboCop
|
|
149
149
|
return if part_of_ignored_node?(node)
|
150
150
|
|
151
151
|
predicate_matcher?(node) do |actual, matcher|
|
152
|
+
next unless replaceable_matcher?(matcher)
|
153
|
+
|
152
154
|
add_offense(node, message: message_explicit(matcher)) do |corrector|
|
155
|
+
next if uncorrectable_matcher?(node, matcher)
|
156
|
+
|
153
157
|
corrector_explicit(corrector, node, actual, matcher, matcher)
|
154
158
|
end
|
155
159
|
end
|
156
160
|
end
|
157
161
|
|
162
|
+
def replaceable_matcher?(matcher)
|
163
|
+
case matcher.method_name.to_s
|
164
|
+
when 'include'
|
165
|
+
matcher.arguments.one?
|
166
|
+
else
|
167
|
+
true
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def uncorrectable_matcher?(node, matcher)
|
172
|
+
heredoc_argument?(matcher) && !same_line?(node, matcher)
|
173
|
+
end
|
174
|
+
|
175
|
+
def heredoc_argument?(matcher)
|
176
|
+
matcher.arguments.select do |arg|
|
177
|
+
%i[str dstr xstr].include?(arg.type)
|
178
|
+
end.any?(&:heredoc?)
|
179
|
+
end
|
180
|
+
|
158
181
|
# @!method predicate_matcher?(node)
|
159
182
|
def_node_matcher :predicate_matcher?, <<-PATTERN
|
160
183
|
(send
|
@@ -271,6 +294,17 @@ module RuboCop
|
|
271
294
|
# # good - the above code is rewritten to it by this cop
|
272
295
|
# expect(foo.something?).to be(true)
|
273
296
|
#
|
297
|
+
# # bad - no autocorrect
|
298
|
+
# expect(foo)
|
299
|
+
# .to be_something(<<~TEXT)
|
300
|
+
# bar
|
301
|
+
# TEXT
|
302
|
+
#
|
303
|
+
# # good
|
304
|
+
# expect(foo.something?(<<~TEXT)).to be(true)
|
305
|
+
# bar
|
306
|
+
# TEXT
|
307
|
+
#
|
274
308
|
# @example Strict: false, EnforcedStyle: explicit
|
275
309
|
# # bad
|
276
310
|
# expect(foo).to be_something
|
@@ -8,6 +8,15 @@ module RuboCop
|
|
8
8
|
class ConfigFormatter
|
9
9
|
EXTENSION_ROOT_DEPARTMENT = %r{^(RSpec/)}.freeze
|
10
10
|
SUBDEPARTMENTS = %(RSpec/Capybara RSpec/FactoryBot RSpec/Rails)
|
11
|
+
EXTRACTED_COPS = %(
|
12
|
+
RSpec/Capybara/CurrentPathExpectation
|
13
|
+
RSpec/Capybara/MatchStyle
|
14
|
+
RSpec/Capybara/NegationMatcher
|
15
|
+
RSpec/Capybara/SpecificActions
|
16
|
+
RSpec/Capybara/SpecificFinders
|
17
|
+
RSpec/Capybara/SpecificMatcher
|
18
|
+
RSpec/Capybara/VisibilityMatcher
|
19
|
+
)
|
11
20
|
AMENDMENTS = %(Metrics/BlockLength)
|
12
21
|
COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/'
|
13
22
|
|
@@ -29,6 +38,7 @@ module RuboCop
|
|
29
38
|
def unified_config
|
30
39
|
cops.each_with_object(config.dup) do |cop, unified|
|
31
40
|
next if SUBDEPARTMENTS.include?(cop) || AMENDMENTS.include?(cop)
|
41
|
+
next if EXTRACTED_COPS.include?(cop)
|
32
42
|
|
33
43
|
replace_nil(unified[cop])
|
34
44
|
unified[cop].merge!(descriptions.fetch(cop))
|
data/lib/rubocop-rspec.rb
CHANGED
@@ -4,6 +4,7 @@ require 'pathname'
|
|
4
4
|
require 'yaml'
|
5
5
|
|
6
6
|
require 'rubocop'
|
7
|
+
require 'rubocop-capybara'
|
7
8
|
|
8
9
|
require_relative 'rubocop/rspec'
|
9
10
|
require_relative 'rubocop/rspec/inject'
|
@@ -17,8 +18,6 @@ require_relative 'rubocop/rspec/language'
|
|
17
18
|
|
18
19
|
require_relative 'rubocop/rspec/factory_bot/language'
|
19
20
|
|
20
|
-
require_relative 'rubocop/cop/rspec/mixin/capybara_help'
|
21
|
-
require_relative 'rubocop/cop/rspec/mixin/css_selector'
|
22
21
|
require_relative 'rubocop/cop/rspec/mixin/final_end_location'
|
23
22
|
require_relative 'rubocop/cop/rspec/mixin/inside_example_group'
|
24
23
|
require_relative 'rubocop/cop/rspec/mixin/metadata'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-rspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.18.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Backus
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2023-01-
|
13
|
+
date: 2023-01-16 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rubocop
|
@@ -26,6 +26,20 @@ dependencies:
|
|
26
26
|
- - "~>"
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
version: '1.33'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: rubocop-capybara
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
29
43
|
description: |2
|
30
44
|
Code style checking for RSpec files.
|
31
45
|
A plugin for the RuboCop code style enforcing & linting tool.
|
@@ -116,9 +130,7 @@ files:
|
|
116
130
|
- lib/rubocop/cop/rspec/message_expectation.rb
|
117
131
|
- lib/rubocop/cop/rspec/message_spies.rb
|
118
132
|
- lib/rubocop/cop/rspec/missing_example_group_argument.rb
|
119
|
-
- lib/rubocop/cop/rspec/mixin/capybara_help.rb
|
120
133
|
- lib/rubocop/cop/rspec/mixin/comments_help.rb
|
121
|
-
- lib/rubocop/cop/rspec/mixin/css_selector.rb
|
122
134
|
- lib/rubocop/cop/rspec/mixin/empty_line_separation.rb
|
123
135
|
- lib/rubocop/cop/rspec/mixin/final_end_location.rb
|
124
136
|
- lib/rubocop/cop/rspec/mixin/inside_example_group.rb
|
@@ -1,80 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RuboCop
|
4
|
-
module Cop
|
5
|
-
module RSpec
|
6
|
-
# Help methods for capybara.
|
7
|
-
module CapybaraHelp
|
8
|
-
module_function
|
9
|
-
|
10
|
-
# @param node [RuboCop::AST::SendNode]
|
11
|
-
# @param locator [String]
|
12
|
-
# @param element [String]
|
13
|
-
# @return [Boolean]
|
14
|
-
def specific_option?(node, locator, element)
|
15
|
-
attrs = CssSelector.attributes(locator).keys
|
16
|
-
return false unless replaceable_element?(node, element, attrs)
|
17
|
-
|
18
|
-
attrs.all? do |attr|
|
19
|
-
CssSelector.specific_options?(element, attr)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# @param locator [String]
|
24
|
-
# @return [Boolean]
|
25
|
-
def specific_pseudo_classes?(locator)
|
26
|
-
CssSelector.pseudo_classes(locator).all? do |pseudo_class|
|
27
|
-
replaceable_pseudo_class?(pseudo_class, locator)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# @param pseudo_class [String]
|
32
|
-
# @param locator [String]
|
33
|
-
# @return [Boolean]
|
34
|
-
def replaceable_pseudo_class?(pseudo_class, locator)
|
35
|
-
return false unless CssSelector.specific_pesudo_classes?(pseudo_class)
|
36
|
-
|
37
|
-
case pseudo_class
|
38
|
-
when 'not()' then replaceable_pseudo_class_not?(locator)
|
39
|
-
else true
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# @param locator [String]
|
44
|
-
# @return [Boolean]
|
45
|
-
def replaceable_pseudo_class_not?(locator)
|
46
|
-
locator.scan(/not\(.*?\)/).all? do |negation|
|
47
|
-
CssSelector.attributes(negation).values.all? do |v|
|
48
|
-
v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# @param node [RuboCop::AST::SendNode]
|
54
|
-
# @param element [String]
|
55
|
-
# @param attrs [Array<String>]
|
56
|
-
# @return [Boolean]
|
57
|
-
def replaceable_element?(node, element, attrs)
|
58
|
-
case element
|
59
|
-
when 'link' then replaceable_to_link?(node, attrs)
|
60
|
-
else true
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# @param node [RuboCop::AST::SendNode]
|
65
|
-
# @param attrs [Array<String>]
|
66
|
-
# @return [Boolean]
|
67
|
-
def replaceable_to_link?(node, attrs)
|
68
|
-
include_option?(node, :href) || attrs.include?('href')
|
69
|
-
end
|
70
|
-
|
71
|
-
# @param node [RuboCop::AST::SendNode]
|
72
|
-
# @param option [Symbol]
|
73
|
-
# @return [Boolean]
|
74
|
-
def include_option?(node, option)
|
75
|
-
node.each_descendant(:sym).find { |opt| opt.value == option }
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,146 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RuboCop
|
4
|
-
module Cop
|
5
|
-
module RSpec
|
6
|
-
# Helps parsing css selector.
|
7
|
-
module CssSelector
|
8
|
-
COMMON_OPTIONS = %w[
|
9
|
-
above below left_of right_of near count minimum maximum between text
|
10
|
-
id class style visible obscured exact exact_text normalize_ws match
|
11
|
-
wait filter_set focused
|
12
|
-
].freeze
|
13
|
-
SPECIFIC_OPTIONS = {
|
14
|
-
'button' => (
|
15
|
-
COMMON_OPTIONS + %w[disabled name value title type]
|
16
|
-
).freeze,
|
17
|
-
'link' => (
|
18
|
-
COMMON_OPTIONS + %w[href alt title download]
|
19
|
-
).freeze,
|
20
|
-
'table' => (
|
21
|
-
COMMON_OPTIONS + %w[
|
22
|
-
caption with_cols cols with_rows rows
|
23
|
-
]
|
24
|
-
).freeze,
|
25
|
-
'select' => (
|
26
|
-
COMMON_OPTIONS + %w[
|
27
|
-
disabled name placeholder options enabled_options
|
28
|
-
disabled_options selected with_selected multiple with_options
|
29
|
-
]
|
30
|
-
).freeze,
|
31
|
-
'field' => (
|
32
|
-
COMMON_OPTIONS + %w[
|
33
|
-
checked unchecked disabled valid name placeholder
|
34
|
-
validation_message readonly with type multiple
|
35
|
-
]
|
36
|
-
).freeze
|
37
|
-
}.freeze
|
38
|
-
SPECIFIC_PSEUDO_CLASSES = %w[
|
39
|
-
not() disabled enabled checked unchecked
|
40
|
-
].freeze
|
41
|
-
|
42
|
-
module_function
|
43
|
-
|
44
|
-
# @param element [String]
|
45
|
-
# @param attribute [String]
|
46
|
-
# @return [Boolean]
|
47
|
-
# @example
|
48
|
-
# specific_pesudo_classes?('button', 'name') # => true
|
49
|
-
# specific_pesudo_classes?('link', 'invalid') # => false
|
50
|
-
def specific_options?(element, attribute)
|
51
|
-
SPECIFIC_OPTIONS.fetch(element, []).include?(attribute)
|
52
|
-
end
|
53
|
-
|
54
|
-
# @param pseudo_class [String]
|
55
|
-
# @return [Boolean]
|
56
|
-
# @example
|
57
|
-
# specific_pesudo_classes?('disabled') # => true
|
58
|
-
# specific_pesudo_classes?('first-of-type') # => false
|
59
|
-
def specific_pesudo_classes?(pseudo_class)
|
60
|
-
SPECIFIC_PSEUDO_CLASSES.include?(pseudo_class)
|
61
|
-
end
|
62
|
-
|
63
|
-
# @param selector [String]
|
64
|
-
# @return [Boolean]
|
65
|
-
# @example
|
66
|
-
# id?('#some-id') # => true
|
67
|
-
# id?('.some-class') # => false
|
68
|
-
def id?(selector)
|
69
|
-
selector.start_with?('#')
|
70
|
-
end
|
71
|
-
|
72
|
-
# @param selector [String]
|
73
|
-
# @return [Boolean]
|
74
|
-
# @example
|
75
|
-
# attribute?('[attribute]') # => true
|
76
|
-
# attribute?('attribute') # => false
|
77
|
-
def attribute?(selector)
|
78
|
-
selector.start_with?('[')
|
79
|
-
end
|
80
|
-
|
81
|
-
# @param selector [String]
|
82
|
-
# @return [Array<String>]
|
83
|
-
# @example
|
84
|
-
# attributes('a[foo-bar_baz]') # => {"foo-bar_baz=>true}
|
85
|
-
# attributes('button[foo][bar]') # => {"foo"=>true, "bar"=>true}
|
86
|
-
# attributes('table[foo=bar]') # => {"foo"=>"'bar'"}
|
87
|
-
def attributes(selector)
|
88
|
-
selector.scan(/\[(.*?)\]/).flatten.to_h do |attr|
|
89
|
-
key, value = attr.split('=')
|
90
|
-
[key, normalize_value(value)]
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
# @param selector [String]
|
95
|
-
# @return [Boolean]
|
96
|
-
# @example
|
97
|
-
# common_attributes?('a[focused]') # => true
|
98
|
-
# common_attributes?('button[focused][visible]') # => true
|
99
|
-
# common_attributes?('table[id=some-id]') # => true
|
100
|
-
# common_attributes?('h1[invalid]') # => false
|
101
|
-
def common_attributes?(selector)
|
102
|
-
attributes(selector).keys.difference(COMMON_OPTIONS).none?
|
103
|
-
end
|
104
|
-
|
105
|
-
# @param selector [String]
|
106
|
-
# @return [Array<String>]
|
107
|
-
# @example
|
108
|
-
# pseudo_classes('button:not([disabled])') # => ['not()']
|
109
|
-
# pseudo_classes('a:enabled:not([valid])') # => ['enabled', 'not()']
|
110
|
-
def pseudo_classes(selector)
|
111
|
-
# Attributes must be excluded or else the colon in the `href`s URL
|
112
|
-
# will also be picked up as pseudo classes.
|
113
|
-
# "a:not([href='http://example.com']):enabled" => "a:not():enabled"
|
114
|
-
ignored_attribute = selector.gsub(/\[.*?\]/, '')
|
115
|
-
# "a:not():enabled" => ["not()", "enabled"]
|
116
|
-
ignored_attribute.scan(/:([^:]*)/).flatten
|
117
|
-
end
|
118
|
-
|
119
|
-
# @param selector [String]
|
120
|
-
# @return [Boolean]
|
121
|
-
# @example
|
122
|
-
# multiple_selectors?('a.cls b#id') # => true
|
123
|
-
# multiple_selectors?('a.cls') # => false
|
124
|
-
def multiple_selectors?(selector)
|
125
|
-
selector.match?(/[ >,+~]/)
|
126
|
-
end
|
127
|
-
|
128
|
-
# @param value [String]
|
129
|
-
# @return [Boolean, String]
|
130
|
-
# @example
|
131
|
-
# normalize_value('true') # => true
|
132
|
-
# normalize_value('false') # => false
|
133
|
-
# normalize_value(nil) # => false
|
134
|
-
# normalize_value("foo") # => "'foo'"
|
135
|
-
def normalize_value(value)
|
136
|
-
case value
|
137
|
-
when 'true' then true
|
138
|
-
when 'false' then false
|
139
|
-
when nil then true
|
140
|
-
else "'#{value}'"
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|