rubocop-capybara 2.23.0 → 3.1.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/README.md +1 -1
  4. data/config/default.yml +97 -32
  5. data/lib/rubocop/capybara/plugin.rb +2 -3
  6. data/lib/rubocop/capybara/version.rb +1 -1
  7. data/lib/rubocop/cop/capybara/absolute_x_path_in_element_scope.rb +104 -0
  8. data/lib/rubocop/cop/capybara/always_true_count_query.rb +75 -0
  9. data/lib/rubocop/cop/capybara/ambiguous_click.rb +1 -1
  10. data/lib/rubocop/cop/capybara/assert_style.rb +1 -1
  11. data/lib/rubocop/cop/capybara/chained_find.rb +175 -0
  12. data/lib/rubocop/cop/capybara/find_all_first.rb +13 -41
  13. data/lib/rubocop/cop/capybara/ignored_query_option.rb +242 -0
  14. data/lib/rubocop/cop/capybara/mixin/capybara_help.rb +86 -2
  15. data/lib/rubocop/cop/capybara/mixin/css_attributes_parser.rb +23 -5
  16. data/lib/rubocop/cop/capybara/mixin/css_selector.rb +13 -10
  17. data/lib/rubocop/cop/capybara/mixin/query_methods.rb +120 -0
  18. data/lib/rubocop/cop/capybara/mixin/visibility.rb +53 -0
  19. data/lib/rubocop/cop/capybara/modal_method_without_block.rb +37 -0
  20. data/lib/rubocop/cop/capybara/native_attribute.rb +76 -0
  21. data/lib/rubocop/cop/capybara/redundant_within_find.rb +10 -21
  22. data/lib/rubocop/cop/capybara/rspec/current_path_expectation.rb +13 -16
  23. data/lib/rubocop/cop/capybara/rspec/current_url_expectation.rb +122 -0
  24. data/lib/rubocop/cop/capybara/rspec/find_element_value_eq.rb +173 -0
  25. data/lib/rubocop/cop/capybara/rspec/have_content.rb +1 -1
  26. data/lib/rubocop/cop/capybara/rspec/have_selector.rb +26 -15
  27. data/lib/rubocop/cop/capybara/rspec/match_style.rb +1 -1
  28. data/lib/rubocop/cop/capybara/rspec/negation_matcher.rb +1 -1
  29. data/lib/rubocop/cop/capybara/rspec/negation_matcher_after_visit.rb +2 -2
  30. data/lib/rubocop/cop/capybara/rspec/predicate_matcher.rb +43 -7
  31. data/lib/rubocop/cop/capybara/rspec/specific_matcher.rb +8 -2
  32. data/lib/rubocop/cop/capybara/rspec/visibility_matcher.rb +29 -24
  33. data/lib/rubocop/cop/capybara/rspec.rb +25 -0
  34. data/lib/rubocop/cop/capybara/specific_actions.rb +12 -7
  35. data/lib/rubocop/cop/capybara/specific_finders.rb +149 -28
  36. data/lib/rubocop/cop/capybara/unordered_window_access.rb +40 -0
  37. data/lib/rubocop/cop/capybara/visibility_option.rb +64 -0
  38. data/lib/rubocop/cop/capybara.rb +28 -0
  39. data/lib/rubocop/cop/capybara_cops.rb +4 -17
  40. data/lib/rubocop-capybara.rb +7 -5
  41. metadata +18 -6
  42. data/config/obsoletion.yml +0 -18
  43. data/lib/rubocop/cop/capybara/click_link_or_button_style.rb +0 -87
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d1c9d6e62bd38cf7ef34d42dcf3eabcc49cbb2ad1cb23ea96ddfb6d7b964ef0
4
- data.tar.gz: 5fe88f1d98cb8b1068c9604648f1ef18517fcd27b3552a7525913f5bcd30490d
3
+ metadata.gz: 5b880f22effca543e3fcbabf13ff06e0055eabeddec7342ec5ff2375a6ceb336
4
+ data.tar.gz: b4937b0aa90a9bb9e095ca0d19418a7ee294bf1bc6cabc84c2001bd8fa46aa92
5
5
  SHA512:
6
- metadata.gz: 5f1652a43814b536fd98975128b51468b453f2665f32f97e8eb8ba9940ff92b7dd03093d29a738356bf50e11349d6b11c7e225cc23e4306bac1d94da48fd0948
7
- data.tar.gz: 9045ef2ed4d00354ca9888158c6dc6088361f476aef5105dfb120421951252d101470cdd68bdcaf71b0ae2fbebe265fab86f6b1ca0fb863603b5465f8a5c3ccb
6
+ metadata.gz: d141ff264d07fb784bc62ec4d47f04153b033e599d3297a1f9b063c8398b877e507cdb3e575c3497ff96612b4acea6ad15a82694b28c03913fd1e59b02423c91
7
+ data.tar.gz: 04abfbff88016558648d3cff263ce0b3ef2413dda3a3f381849d26e3a3bbffcd4b7762c76020d8db0892609e2585af7a0413b41fd20712caec161738f3fa852d
data/CHANGELOG.md CHANGED
@@ -2,6 +2,59 @@
2
2
 
3
3
  ## Edge (Unreleased)
4
4
 
5
+ ## 3.1.0 (2026-10-16)
6
+
7
+ - Add new `Capybara/UnorderedWindowAccess` cop. ([@ydah])
8
+ - Add new `Capybara/AbsoluteXPathInElementScope` cop. ([@ydah])
9
+ - Add new `Capybara/AlwaysTrueCountQuery` cop. ([@ydah])
10
+ - Add new `Capybara/RSpec/CurrentUrlExpectation` cop. ([@ydah])
11
+ - Add new `Capybara/ModalMethodWithoutBlock` cop. ([@ydah])
12
+ - Add new `Capybara/NativeAttribute` cop. ([@ydah])
13
+ - Add new `Capybara/VisibilityOption` cop. ([@ydah])
14
+ - Add new `Capybara/ChainedFind` cop. ([@ydah])
15
+ - Add new `Capybara/IgnoredQueryOption` cop. ([@ydah])
16
+ - Add new `Capybara/RSpec/FindElementValueEq` cop. ([@ydah])
17
+ - Recognize `have_element` and `have_no_element` in the RSpec negation cops. ([@ydah])
18
+ - Detect `have_no_selector` in `Capybara/RSpec/HaveSelector`. ([@ydah])
19
+ - Autocorrect `visible: true` in `Capybara/RSpec/VisibilityMatcher` for known selectors. ([@ydah])
20
+ - Add support for using `find` with `:link` and `:field` in `Capybara/SpecificFinders` cop. ([@ydah])
21
+ - Speed up loading rubocop-capybara by lazily loading only the cops needed for a run. This requires RuboCop 1.89.0+. ([@koic])
22
+ - Fix false positives in `Capybara/RSpec/SpecificMatcher` for input types excluded by `have_field`. ([@ydah])
23
+ - Avoid interpolating literal `#{...}`, `#@...`, and `#$...` patterns in `Capybara/RSpec/CurrentPathExpectation` and `Capybara/RSpec/CurrentUrlExpectation` autocorrection. ([@ydah])
24
+ - Fix false positives and negatives for `Capybara/SpecificActions` when CSS attribute values contain spaces. ([@ydah])
25
+ - Honor `DefaultSelector: null` in selector-dependent cops, including `Capybara/RSpec/HaveSelector`. ([@ydah])
26
+ - Fix false positives for custom elements in `Capybara/SpecificActions` and `Capybara/RSpec/SpecificMatcher`. ([@ydah])
27
+ - Avoid unsafe block autocorrection and unnecessary receiver parentheses in `Capybara/RSpec/PredicateMatcher`. ([@ydah])
28
+ - Fix a crash in `Capybara/RSpec/CurrentPathExpectation` for invalid regexp strings. ([@ydah])
29
+ - Fix `Capybara/SpecificFinders` when CSS id values are `true` or `false`. ([@ydah])
30
+ - Fix a crash in `Capybara/SpecificActions` for blank `find` selectors. ([@ydah])
31
+ - Fix reversed autocorrection in `Capybara/RSpec/PredicateMatcher` for `eql` and `equal` boolean matchers. ([@ydah])
32
+ - Avoid errors and unsafe autocorrection in `Capybara/SpecificFinders` with dynamic `class:` options. ([@ydah])
33
+ - Fix incorrect autocorrections and missed offenses for non-CSS locators in `Capybara/SpecificFinders`. ([@ydah])
34
+ - Use the `:id` selector when `Capybara/RedundantWithinFind` autocorrects `find_by_id`, preserving special and dynamic IDs. ([@ydah])
35
+ - Avoid unsafe `find_field` autocorrections for input types and boolean attributes in `Capybara/SpecificFinders`. ([@ydah])
36
+ - Fix false positives and incorrect autocorrections for `Capybara/FindAllFirst` when `find` or `all` uses `match: :first`. ([@ydah])
37
+ - Avoid `Capybara/FindAllFirst` offenses when the result is used with safe navigation or as a condition. ([@ydah])
38
+ - Fix an incorrect autocorrect for `Capybara/RedundantWithinFind` when `find_by_id` uses a dynamic id. ([@ydah])
39
+ - Fix an incorrect autocorrect for `Capybara/RedundantWithinFind` when a finder call has a block argument. ([@ydah])
40
+ - Fix a false positive for `Capybara/RSpec/HaveSelector` when `DefaultSelector` is unsupported. ([@ydah])
41
+ - Fix an incorrect autocorrect for `Capybara/SpecificFinders` when a selector class is empty or contains an escaped dot. ([@ydah])
42
+ - Fix a false positive for `Capybara/SpecificFinders` when an id selector contains unsupported CSS identifier syntax. ([@ydah])
43
+ - Fix a false negative for `Capybara/RSpec/NegationMatcherAfterVisit` when using `to_not`. ([@ydah])
44
+ - Fix an incorrect autocorrect for `Capybara/RSpec/PredicateMatcher` when `EnforcedStyle: explicit` and `expect` actual is a complex expression. ([@ydah])
45
+ - Fix `Capybara/RSpec/CurrentPathExpectation` autocorrection for `match` when the URL has a query string. ([@ydah])
46
+ - Fix a false positive for `Capybara/SpecificActions` when `click` uses arguments or a block. ([@ydah])
47
+ - Fix an incorrect autocorrect for `Capybara/SpecificFinders` when a selector class is used with nested `class:` options. ([@ydah])
48
+ - Fix CSS attribute parsing when attribute values contain quotes. ([@ydah])
49
+ - Fix CSS attribute parsing to return raw values and format Ruby literals in autocorrection. ([@ydah])
50
+ - Fix a false negative for `Capybara/SpecificFinders` when using `find` with input field attributes. ([@ydah])
51
+
52
+ ## 3.0.0 (2026-06-22)
53
+
54
+ - Enable pending cops by default for the 3.0 release. ([@ydah])
55
+ - Remove the deprecated `Capybara/ClickLinkOrButtonStyle` cop. ([@ydah])
56
+ - Remove obsolete cop name migration config for the 2.x to 3.0 transition. ([@ydah])
57
+
5
58
  ## 2.23.0 (2026-04-30)
6
59
 
7
60
  - Bump RuboCop requirement to +1.81. ([@ydah])
data/README.md CHANGED
@@ -76,7 +76,7 @@ In your `.rubocop.yml`, you may treat the Capybara cops just like any other
76
76
  cop. For example:
77
77
 
78
78
  ```yaml
79
- Capybara/SpecificMatcher:
79
+ Capybara/RSpec/SpecificMatcher:
80
80
  Exclude:
81
81
  - spec/my_spec.rb
82
82
  ```
data/config/default.yml CHANGED
@@ -9,6 +9,18 @@ Capybara:
9
9
  - "**/*_steps.rb"
10
10
  - "**/features/step_definitions/**/*"
11
11
 
12
+ Capybara/AbsoluteXPathInElementScope:
13
+ Description: Checks for absolute XPath searches on Capybara elements.
14
+ Enabled: pending
15
+ VersionAdded: '3.1'
16
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/AbsoluteXPathInElementScope
17
+
18
+ Capybara/AlwaysTrueCountQuery:
19
+ Description: Checks for positive queries that succeed when there are no matches.
20
+ Enabled: pending
21
+ VersionAdded: '3.1'
22
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/AlwaysTrueCountQuery
23
+
12
24
  Capybara/AmbiguousClick:
13
25
  Description: Specify the exact target to click on.
14
26
  Enabled: false
@@ -17,82 +29,132 @@ Capybara/AmbiguousClick:
17
29
 
18
30
  Capybara/AssertStyle:
19
31
  Description: Checks for usage of deprecated assert style method.
20
- Enabled: pending
32
+ Enabled: true
21
33
  VersionAdded: '2.23'
34
+ VersionChanged: '3.0'
22
35
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/AssertStyle
23
36
 
24
- Capybara/ClickLinkOrButtonStyle:
25
- Description: Checks for methods of button or link clicks.
26
- Enabled: false
27
- VersionAdded: '2.19'
28
- VersionChanged: '2.22'
29
- EnforcedStyle: link_or_button
30
- SupportedStyles:
31
- - link_or_button
32
- - strict
33
- Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/ClickLinkOrButtonStyle
37
+ Capybara/ChainedFind:
38
+ Description: Avoid chaining `find` methods and combine selectors.
39
+ Enabled: pending
40
+ DefaultSelector: css
41
+ VersionAdded: '3.1'
42
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/ChainedFind
34
43
 
35
44
  Capybara/FindAllFirst:
36
45
  Description: Enforces use of `first` instead of `all` with `first` or `[0]`.
37
- Enabled: pending
46
+ Enabled: true
38
47
  SafeAutoCorrect: false
39
48
  VersionAdded: '2.22'
40
- VersionChanged: '2.23'
49
+ VersionChanged: '3.0'
41
50
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/FindAllFirst
42
51
 
52
+ Capybara/IgnoredQueryOption:
53
+ Description: Checks for query options that have no effect in Capybara.
54
+ Enabled: pending
55
+ DefaultSelector: css
56
+ VersionAdded: '3.1'
57
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/IgnoredQueryOption
58
+
59
+ Capybara/ModalMethodWithoutBlock:
60
+ Description: Checks that modal handling methods are called with a block.
61
+ Enabled: pending
62
+ VersionAdded: '3.1'
63
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/ModalMethodWithoutBlock
64
+
65
+ Capybara/NativeAttribute:
66
+ Description: Enforces use of `Element#[]` instead of `native.attribute`.
67
+ Enabled: pending
68
+ SafeAutoCorrect: false
69
+ VersionAdded: '3.1'
70
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NativeAttribute
71
+
43
72
  Capybara/RedundantWithinFind:
44
73
  Description: Checks for redundant `within find(...)` calls.
45
- Enabled: pending
74
+ Enabled: true
46
75
  VersionAdded: '2.20'
76
+ VersionChanged: '3.0'
47
77
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RedundantWithinFind
48
78
 
49
79
  Capybara/SpecificActions:
50
80
  Description: Checks for there is a more specific actions offered by Capybara.
51
- Enabled: pending
81
+ Enabled: true
52
82
  VersionAdded: '2.14'
83
+ VersionChanged: '3.0'
53
84
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/SpecificActions
54
85
 
55
86
  Capybara/SpecificFinders:
56
87
  Description: Checks if there is a more specific finder offered by Capybara.
57
- Enabled: pending
88
+ Enabled: true
58
89
  VersionAdded: '2.13'
90
+ VersionChanged: '3.0'
59
91
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/SpecificFinders
60
92
 
93
+ Capybara/UnorderedWindowAccess:
94
+ Description: Checks positional access to Capybara windows, whose order is undefined.
95
+ Enabled: pending
96
+ VersionAdded: '3.1'
97
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/UnorderedWindowAccess
98
+
99
+ Capybara/VisibilityOption:
100
+ Description: Checks for boolean values passed to Capybara's `visible` option.
101
+ Enabled: pending
102
+ DefaultSelector: css
103
+ VersionAdded: '3.1'
104
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/VisibilityOption
105
+
61
106
  Capybara/RSpec:
62
107
  Enabled: true
63
108
  Include: *1
64
109
 
65
110
  Capybara/RSpec/CurrentPathExpectation:
66
111
  Description: Checks that no expectations are set on Capybara's `current_path`.
67
- Enabled: pending
112
+ Enabled: true
68
113
  VersionAdded: '1.18'
69
- VersionChanged: '2.23'
114
+ VersionChanged: '3.0'
70
115
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/CurrentPathExpectation
71
116
 
117
+ Capybara/RSpec/CurrentUrlExpectation:
118
+ Description: Checks expectations on `current_url` that bypass Capybara's waiting.
119
+ Enabled: pending
120
+ SafeAutoCorrect: false
121
+ VersionAdded: '3.1'
122
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/CurrentUrlExpectation
123
+
124
+ Capybara/RSpec/FindElementValueEq:
125
+ Description: Checks for expectations on the value of an element found by Capybara.
126
+ Enabled: pending
127
+ SafeAutoCorrect: false
128
+ VersionAdded: '3.1'
129
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/FindElementValueEq
130
+
72
131
  Capybara/RSpec/HaveContent:
73
132
  Description: Checks for usage of `have_content` and `have_no_content`.
74
- Enabled: pending
133
+ Enabled: true
75
134
  VersionAdded: '2.23'
135
+ VersionChanged: '3.0'
76
136
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/HaveContent
77
137
 
78
138
  Capybara/RSpec/HaveSelector:
79
- Description: Use `have_css` or `have_xpath` instead of `have_selector`.
80
- Enabled: pending
139
+ Description: Use CSS or XPath matchers instead of generic selector matchers.
140
+ Enabled: true
81
141
  DefaultSelector: css
82
142
  VersionAdded: '2.19'
143
+ VersionChanged: '3.0'
83
144
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/HaveSelector
84
145
 
85
146
  Capybara/RSpec/MatchStyle:
86
147
  Description: Checks for usage of deprecated style methods in RSpec matchers.
87
- Enabled: pending
148
+ Enabled: true
88
149
  VersionAdded: '2.23'
150
+ VersionChanged: '3.0'
89
151
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/MatchStyle
90
152
 
91
153
  Capybara/RSpec/NegationMatcher:
92
154
  Description: Enforces use of `have_no_*` or `not_to` for negated expectations.
93
- Enabled: pending
155
+ Enabled: true
94
156
  VersionAdded: '2.14'
95
- VersionChanged: '2.23'
157
+ VersionChanged: '3.0'
96
158
  EnforcedStyle: have_no
97
159
  SupportedStyles:
98
160
  - have_no
@@ -101,14 +163,14 @@ Capybara/RSpec/NegationMatcher:
101
163
 
102
164
  Capybara/RSpec/NegationMatcherAfterVisit:
103
165
  Description: Do not allow negative matchers to be used immediately after `visit`.
104
- Enabled: pending
166
+ Enabled: true
105
167
  VersionAdded: '2.22'
106
- VersionChanged: '2.23'
168
+ VersionChanged: '3.0'
107
169
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/NegationMatcherAfterVisit
108
170
 
109
171
  Capybara/RSpec/PredicateMatcher:
110
172
  Description: Prefer using predicate matcher over using predicate method directly.
111
- Enabled: pending
173
+ Enabled: true
112
174
  Strict: true
113
175
  EnforcedStyle: inflected
114
176
  AllowedExplicitMatchers: []
@@ -116,18 +178,21 @@ Capybara/RSpec/PredicateMatcher:
116
178
  - inflected
117
179
  - explicit
118
180
  VersionAdded: '2.19'
181
+ VersionChanged: '3.0'
119
182
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/PredicateMatcher
120
183
 
121
184
  Capybara/RSpec/SpecificMatcher:
122
185
  Description: Checks for there is a more specific matcher offered by Capybara.
123
- Enabled: pending
186
+ Enabled: true
124
187
  VersionAdded: '2.12'
125
- VersionChanged: '2.23'
188
+ VersionChanged: '3.0'
126
189
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/SpecificMatcher
127
190
 
128
191
  Capybara/RSpec/VisibilityMatcher:
129
- Description: Checks for boolean visibility in Capybara finders.
130
- Enabled: pending
192
+ Description: Checks for boolean visibility in Capybara RSpec matchers.
193
+ Enabled: true
194
+ SafeAutoCorrect: false
195
+ DefaultSelector: css
131
196
  VersionAdded: '1.39'
132
- VersionChanged: '2.23'
197
+ VersionChanged: "<<next>>"
133
198
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/VisibilityMatcher
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
4
+
3
5
  require 'lint_roller'
4
6
 
5
7
  module RuboCop
@@ -24,9 +26,6 @@ module RuboCop
24
26
  def rules(_context)
25
27
  project_root = Pathname.new(__dir__).join('../../..')
26
28
 
27
- obsoletion = project_root.join('config', 'obsoletion.yml')
28
- ConfigObsoletion.files << obsoletion if obsoletion.exist?
29
-
30
29
  LintRoller::Rules.new(
31
30
  type: :path,
32
31
  config_format: :rubocop,
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Capybara
5
5
  # Version information for the Capybara RuboCop plugin.
6
6
  module Version
7
- STRING = '2.23.0'
7
+ STRING = '3.1.0'
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ # Checks for absolute XPath searches on Capybara elements.
7
+ #
8
+ # An XPath starting with `//` searches from the document root, even when
9
+ # the query is called on an element. Use `.//` to stay within it.
10
+ # Direct RSpec `let` and named `subject` element definitions are
11
+ # recognized.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # page.find('.dialog').find(:xpath, '//button')
16
+ # dialog = page.find('.dialog')
17
+ # dialog.find(:xpath, '//button')
18
+ # RSpec.describe 'dialog' do
19
+ # let(:dialog) { find('.dialog') }
20
+ # it { dialog.find(:xpath, '//button') }
21
+ # end
22
+ #
23
+ # # good
24
+ # page.find('.dialog').find(:xpath, './/button')
25
+ # page.find(:xpath, '//button')
26
+ #
27
+ class AbsoluteXPathInElementScope < RuboCop::Cop::Base
28
+ include CapybaraHelp
29
+
30
+ MSG = 'Use `.//` for an XPath search within an element.'
31
+ RESTRICT_ON_SEND = %i[find all find_all first].freeze
32
+ RSPEC_GROUPS = %i[
33
+ describe context feature shared_examples shared_context
34
+ ].freeze
35
+ RSPEC_HELPERS = %i[let let! subject].freeze
36
+
37
+ # @!method xpath_literal(node)
38
+ def_node_matcher :xpath_literal, '(call _ _ (sym :xpath) $(str _) ...)'
39
+
40
+ # @!method element_finder?(node)
41
+ def_node_matcher :element_finder?, <<~PATTERN
42
+ (call #capybara_receiver? :find ...)
43
+ PATTERN
44
+
45
+ def on_send(node)
46
+ xpath = xpath_literal(node)
47
+ return unless xpath&.value&.start_with?('//')
48
+ return unless element_receiver?(node.receiver)
49
+
50
+ add_offense(xpath)
51
+ end
52
+ alias on_csend on_send
53
+
54
+ private
55
+
56
+ def element_receiver?(node)
57
+ return true if node&.call_type? && element_finder?(node)
58
+ return element_finder?(local_variable_value(node)) if node&.lvar_type?
59
+
60
+ element_finder?(rspec_helper_value(node)) if node&.call_type?
61
+ end
62
+
63
+ def rspec_helper_value(node)
64
+ return unless node.receiver.nil? && node.arguments.empty?
65
+
66
+ node.each_ancestor(:block).each do |group|
67
+ next unless rspec_group?(group)
68
+
69
+ definition = named_helper(group, node.method_name)
70
+ return definition.body if definition
71
+ end
72
+ nil
73
+ end
74
+
75
+ def rspec_group?(block)
76
+ call = block.send_node
77
+ return false unless RSPEC_GROUPS.include?(call.method_name)
78
+
79
+ call.receiver.nil? || (call.receiver.const_type? &&
80
+ call.receiver.const_name == 'RSpec')
81
+ end
82
+
83
+ def named_helper(group, name)
84
+ body = group.body
85
+ statements = body.begin_type? ? body.children : [body]
86
+ statements.reverse_each.find do |statement|
87
+ named_rspec_helper?(statement, name)
88
+ end
89
+ end
90
+
91
+ def named_rspec_helper?(statement, name)
92
+ return false unless statement.block_type?
93
+
94
+ call = statement.send_node
95
+ return false if call.receiver
96
+ return false unless RSPEC_HELPERS.include?(call.method_name)
97
+
98
+ argument = call.first_argument
99
+ argument&.sym_type? && argument.value == name
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ # Checks for positive queries that succeed when there are no matches.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # expect(page).to have_css('.item', minimum: 0)
11
+ # page.has_css?('.item', minimum: 0)
12
+ #
13
+ # # good
14
+ # expect(page).to have_css('.item')
15
+ # expect(page).to have_css('.item', minimum: 0, maximum: 2)
16
+ # all('.item', minimum: 0)
17
+ #
18
+ class AlwaysTrueCountQuery < RuboCop::Cop::Base
19
+ include CapybaraHelp
20
+
21
+ MSG = '`minimum: 0` makes this positive query succeed with no matches.'
22
+ OTHER_COUNT_OPTIONS = %i[count maximum between].freeze
23
+ RESTRICT_ON_SEND = QueryMethods.names(:selector).select do |name|
24
+ name = name.to_s
25
+ name.start_with?('assert_', 'has_', 'have_', 'must_have_') &&
26
+ !name.start_with?('assert_no_', 'has_no_', 'have_no_')
27
+ end.freeze
28
+
29
+ # @!method zero_minimum?(node)
30
+ def_node_matcher :zero_minimum?, '(pair (sym :minimum) (int 0))'
31
+
32
+ def on_send(node)
33
+ return unless capybara_receiver?(node.receiver)
34
+ return unless positive_usage?(node)
35
+
36
+ minimum = always_true_minimum(node.last_argument)
37
+ add_offense(minimum) if minimum
38
+ end
39
+ alias on_csend on_send
40
+
41
+ private
42
+
43
+ def positive_usage?(node)
44
+ return true unless node.method_name.to_s.start_with?('have_')
45
+
46
+ parent = node.parent
47
+ parent&.send_type? && parent.method?(:to) &&
48
+ parent.first_argument == node
49
+ end
50
+
51
+ def always_true_minimum(options)
52
+ return unless known_options?(options)
53
+
54
+ pairs = options.pairs
55
+ return if pairs.any? { |pair| other_count_option?(pair) }
56
+
57
+ minimum = pairs.select { |pair| pair.key.value == :minimum }
58
+ minimum.first if minimum.one? && zero_minimum?(minimum.first)
59
+ end
60
+
61
+ def known_options?(options)
62
+ return false unless options&.hash_type? && !options.braces?
63
+
64
+ options.children.all? do |child|
65
+ child.pair_type? && child.key.sym_type?
66
+ end
67
+ end
68
+
69
+ def other_count_option?(pair)
70
+ OTHER_COUNT_OPTIONS.include?(pair.key.value) && !pair.value.nil_type?
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # click_link('foo')
18
18
  # click_button('foo')
19
19
  #
20
- class AmbiguousClick < ::RuboCop::Cop::Base
20
+ class AmbiguousClick < RuboCop::Cop::Base
21
21
  MSG = 'Use `click_link` or `click_button` instead of `%<method>s`.'
22
22
  RESTRICT_ON_SEND = %i[click_link_or_button click_on].freeze
23
23
 
@@ -12,7 +12,7 @@ module RuboCop
12
12
  # # good
13
13
  # page.find(:css, '#first').assert_matches_style(display: 'block')
14
14
  #
15
- class AssertStyle < ::RuboCop::Cop::Base
15
+ class AssertStyle < RuboCop::Cop::Base
16
16
  extend AutoCorrector
17
17
 
18
18
  MSG = 'Use `assert_matches_style` instead of `assert_style`.'