rubocop-capybara 2.22.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad193ece2721896d4cb226a3c9de94552e09a2c522f73c178869371649ff32e1
4
- data.tar.gz: 1fefe61d2408bbe3767d8e220198c461cffbc73d9c4dba6a6ae65ccac0f54d5c
3
+ metadata.gz: 984aeaf86c3452b30f2eab372f0ec1e1a6dcd3fb3a2efe6e6d60f4e435a9d84d
4
+ data.tar.gz: d5336d9300145f0803d3883c195cbcbdcecb0a8de54660d3fcf682166d4e8cd0
5
5
  SHA512:
6
- metadata.gz: 4c59e247dac850cc730bf1e7d0f0b090622f75411c9eabd25f7ad6bb553651227021c0c39b3ec490239254353cb992f190c0ab9ad6dfa5b119f155186c7199b3
7
- data.tar.gz: c2b6063f6a2c3e603fe27ac11546d14bb21b96a24f1a18e01ea117370d882edb03c621259e4d41179788b1d77eb2288c5d7109c5ef13e1ef28d065a01a6ae136
6
+ metadata.gz: 2c08f6500d0ce4907ebea18f476a6da57411bd35b3e8c7ebe2be619efd2ef8b03999c7c5724eab4013e7febe656d043e9dd341f2cf1e4dfe8e81b945369d2e57
7
+ data.tar.gz: 578287adeb808e1c534134f17dbf2ebf4e7fe91433b90344f0704168deccc98328da9aca3210b4c653960437b788e36f067bee0b4bf7b85d5d926fbeda00b59a
data/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## Edge (Unreleased)
4
4
 
5
+ ## 3.0.0 (2026-06-22)
6
+
7
+ - Enable pending cops by default for the 3.0 release. ([@ydah])
8
+ - Remove the deprecated `Capybara/ClickLinkOrButtonStyle` cop. ([@ydah])
9
+ - Remove obsolete cop name migration config for the 2.x to 3.0 transition. ([@ydah])
10
+
11
+ ## 2.23.0 (2026-04-30)
12
+
13
+ - Bump RuboCop requirement to +1.81. ([@ydah])
14
+ - Add new `Capybara/RSpec/HaveContent` cop. ([@nzlaura])
15
+ - Move the department associated with `Capybara::RSpecMatchers` to `Capybara/RSpec/*`. ([@ydah])
16
+ - Rename `Capybara/CurrentPathExpectation` to `Capybara/RSpec/CurrentPathExpectation`
17
+ - Rename `Capybara/SpecificMatcher` to `Capybara/RSpec/SpecificMatcher`
18
+ - Rename `Capybara/NegationMatcher` to `Capybara/RSpec/NegationMatcher`
19
+ - Rename `Capybara/NegationMatcherAfterVisit` to `Capybara/RSpec/NegationMatcherAfterVisit`
20
+ - Rename `Capybara/VisibilityMatcher` to `Capybara/RSpec/VisibilityMatcher`
21
+ - Split `Capybara/MatchStyle` into `Capybara/AssertStyle` and `Capybara/RSpec/MatchStyle`. ([@ydah])
22
+ - Fix a false positive for `Capybara/FindAllFirst` when using logical operators with `all('...')[0]`. ([@ydah])
23
+ - Fix an incorrect autocorrect for `Capybara/FindAllFirst` when `find` or `all` with `match: :first` uses a receiver and mark autocorrection as unsafe. ([@ydah])
24
+ - Fix an error for `Capybara/RSpec/HaveSelector` when passing only a selector type. ([@ydah])
25
+
5
26
  ## 2.22.1 (2025-03-12)
6
27
 
7
28
  - Fix incorrect plugin version. ([@koic])
@@ -13,6 +34,7 @@
13
34
  - Add a new `Capybara/NegationMatcherAfterVisit` cop. ([@ydah])
14
35
  - Fix an error for `Capybara/RSpec/HaveSelector` when passing no arguments. ([@earlopain])
15
36
  - Make RuboCop Capybara work as a RuboCop plugin. ([@bquorning])
37
+ - Fix an incorrect autocorrect for `Capybara/RedundantWithinFind` when escape required css selector. ([@ydah])
16
38
 
17
39
  ## 2.21.0 (2024-06-08)
18
40
 
@@ -89,6 +111,7 @@
89
111
  [@darhazer]: https://github.com/Darhazer
90
112
  [@earlopain]: https://github.com/earlopain
91
113
  [@koic]: https://github.com/koic
114
+ [@nzlaura]: https://github.com/nzlaura
92
115
  [@onumis]: https://github.com/onumis
93
116
  [@oskarsezerins]: https://github.com/OskarsEzerins
94
117
  [@pirj]: https://github.com/pirj
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
@@ -15,98 +15,96 @@ 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/ClickLinkOrButtonStyle:
19
- Description: Checks for methods of button or link clicks.
20
- Enabled: false
21
- VersionAdded: '2.19'
22
- VersionChanged: '2.22'
23
- EnforcedStyle: link_or_button
24
- SupportedStyles:
25
- - link_or_button
26
- - strict
27
- Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/ClickLinkOrButtonStyle
28
-
29
- Capybara/CurrentPathExpectation:
30
- Description: Checks that no expectations are set on Capybara's `current_path`.
18
+ Capybara/AssertStyle:
19
+ Description: Checks for usage of deprecated assert style method.
31
20
  Enabled: true
32
- VersionAdded: '1.18'
33
- VersionChanged: '2.0'
34
- Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/CurrentPathExpectation
21
+ VersionAdded: '2.23'
22
+ VersionChanged: '3.0'
23
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/AssertStyle
35
24
 
36
25
  Capybara/FindAllFirst:
37
26
  Description: Enforces use of `first` instead of `all` with `first` or `[0]`.
38
- Enabled: pending
27
+ Enabled: true
28
+ SafeAutoCorrect: false
39
29
  VersionAdded: '2.22'
30
+ VersionChanged: '3.0'
40
31
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/FindAllFirst
41
32
 
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
33
  Capybara/RedundantWithinFind:
66
34
  Description: Checks for redundant `within find(...)` calls.
67
- Enabled: pending
35
+ Enabled: true
68
36
  VersionAdded: '2.20'
37
+ VersionChanged: '3.0'
69
38
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RedundantWithinFind
70
39
 
71
40
  Capybara/SpecificActions:
72
41
  Description: Checks for there is a more specific actions offered by Capybara.
73
- Enabled: pending
42
+ Enabled: true
74
43
  VersionAdded: '2.14'
44
+ VersionChanged: '3.0'
75
45
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/SpecificActions
76
46
 
77
47
  Capybara/SpecificFinders:
78
48
  Description: Checks if there is a more specific finder offered by Capybara.
79
- Enabled: pending
49
+ Enabled: true
80
50
  VersionAdded: '2.13'
51
+ VersionChanged: '3.0'
81
52
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/SpecificFinders
82
53
 
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
54
+ Capybara/RSpec:
55
+ Enabled: true
56
+ Include: *1
88
57
 
89
- Capybara/VisibilityMatcher:
90
- Description: Checks for boolean visibility in Capybara finders.
58
+ Capybara/RSpec/CurrentPathExpectation:
59
+ Description: Checks that no expectations are set on Capybara's `current_path`.
91
60
  Enabled: true
92
- VersionAdded: '1.39'
93
- VersionChanged: '2.0'
94
- Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/VisibilityMatcher
61
+ VersionAdded: '1.18'
62
+ VersionChanged: '3.0'
63
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/CurrentPathExpectation
95
64
 
96
- Capybara/RSpec:
65
+ Capybara/RSpec/HaveContent:
66
+ Description: Checks for usage of `have_content` and `have_no_content`.
97
67
  Enabled: true
98
- Include: *1
68
+ VersionAdded: '2.23'
69
+ VersionChanged: '3.0'
70
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/HaveContent
99
71
 
100
72
  Capybara/RSpec/HaveSelector:
101
73
  Description: Use `have_css` or `have_xpath` instead of `have_selector`.
102
- Enabled: pending
74
+ Enabled: true
103
75
  DefaultSelector: css
104
76
  VersionAdded: '2.19'
77
+ VersionChanged: '3.0'
105
78
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/HaveSelector
106
79
 
80
+ Capybara/RSpec/MatchStyle:
81
+ Description: Checks for usage of deprecated style methods in RSpec matchers.
82
+ Enabled: true
83
+ VersionAdded: '2.23'
84
+ VersionChanged: '3.0'
85
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/MatchStyle
86
+
87
+ Capybara/RSpec/NegationMatcher:
88
+ Description: Enforces use of `have_no_*` or `not_to` for negated expectations.
89
+ Enabled: true
90
+ VersionAdded: '2.14'
91
+ VersionChanged: '3.0'
92
+ EnforcedStyle: have_no
93
+ SupportedStyles:
94
+ - have_no
95
+ - not_to
96
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/NegationMatcher
97
+
98
+ Capybara/RSpec/NegationMatcherAfterVisit:
99
+ Description: Do not allow negative matchers to be used immediately after `visit`.
100
+ Enabled: true
101
+ VersionAdded: '2.22'
102
+ VersionChanged: '3.0'
103
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/NegationMatcherAfterVisit
104
+
107
105
  Capybara/RSpec/PredicateMatcher:
108
106
  Description: Prefer using predicate matcher over using predicate method directly.
109
- Enabled: pending
107
+ Enabled: true
110
108
  Strict: true
111
109
  EnforcedStyle: inflected
112
110
  AllowedExplicitMatchers: []
@@ -114,4 +112,19 @@ Capybara/RSpec/PredicateMatcher:
114
112
  - inflected
115
113
  - explicit
116
114
  VersionAdded: '2.19'
115
+ VersionChanged: '3.0'
117
116
  Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/PredicateMatcher
117
+
118
+ Capybara/RSpec/SpecificMatcher:
119
+ Description: Checks for there is a more specific matcher offered by Capybara.
120
+ Enabled: true
121
+ VersionAdded: '2.12'
122
+ VersionChanged: '3.0'
123
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/SpecificMatcher
124
+
125
+ Capybara/RSpec/VisibilityMatcher:
126
+ Description: Checks for boolean visibility in Capybara finders.
127
+ Enabled: true
128
+ VersionAdded: '1.39'
129
+ VersionChanged: '3.0'
130
+ 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.22.1'
7
+ STRING = '3.0.0'
8
8
  end
9
9
  end
10
10
  end
@@ -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
- add_offense(node,
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(node, "first(#{selector})")
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
- replaced = node.arguments.map(&:source).join(', ')
57
- if node.method?(:find_by_id)
58
- replaced.sub(/\A(["'])/, '\1#')
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
- replaced
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, node.arguments[1]))
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