rubocop-capybara 3.0.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/config/default.yml +71 -3
  4. data/lib/rubocop/capybara/version.rb +1 -1
  5. data/lib/rubocop/cop/capybara/absolute_x_path_in_element_scope.rb +104 -0
  6. data/lib/rubocop/cop/capybara/always_true_count_query.rb +75 -0
  7. data/lib/rubocop/cop/capybara/ambiguous_click.rb +1 -1
  8. data/lib/rubocop/cop/capybara/assert_style.rb +1 -1
  9. data/lib/rubocop/cop/capybara/chained_find.rb +175 -0
  10. data/lib/rubocop/cop/capybara/find_all_first.rb +13 -41
  11. data/lib/rubocop/cop/capybara/ignored_query_option.rb +242 -0
  12. data/lib/rubocop/cop/capybara/mixin/capybara_help.rb +86 -2
  13. data/lib/rubocop/cop/capybara/mixin/css_attributes_parser.rb +23 -5
  14. data/lib/rubocop/cop/capybara/mixin/css_selector.rb +13 -10
  15. data/lib/rubocop/cop/capybara/mixin/query_methods.rb +120 -0
  16. data/lib/rubocop/cop/capybara/mixin/visibility.rb +53 -0
  17. data/lib/rubocop/cop/capybara/modal_method_without_block.rb +37 -0
  18. data/lib/rubocop/cop/capybara/native_attribute.rb +76 -0
  19. data/lib/rubocop/cop/capybara/redundant_within_find.rb +10 -21
  20. data/lib/rubocop/cop/capybara/rspec/current_path_expectation.rb +13 -16
  21. data/lib/rubocop/cop/capybara/rspec/current_url_expectation.rb +122 -0
  22. data/lib/rubocop/cop/capybara/rspec/find_element_value_eq.rb +173 -0
  23. data/lib/rubocop/cop/capybara/rspec/have_content.rb +1 -1
  24. data/lib/rubocop/cop/capybara/rspec/have_selector.rb +26 -15
  25. data/lib/rubocop/cop/capybara/rspec/match_style.rb +1 -1
  26. data/lib/rubocop/cop/capybara/rspec/negation_matcher.rb +1 -1
  27. data/lib/rubocop/cop/capybara/rspec/negation_matcher_after_visit.rb +2 -2
  28. data/lib/rubocop/cop/capybara/rspec/predicate_matcher.rb +43 -7
  29. data/lib/rubocop/cop/capybara/rspec/specific_matcher.rb +8 -2
  30. data/lib/rubocop/cop/capybara/rspec/visibility_matcher.rb +29 -24
  31. data/lib/rubocop/cop/capybara/rspec.rb +25 -0
  32. data/lib/rubocop/cop/capybara/specific_actions.rb +12 -7
  33. data/lib/rubocop/cop/capybara/specific_finders.rb +149 -28
  34. data/lib/rubocop/cop/capybara/unordered_window_access.rb +40 -0
  35. data/lib/rubocop/cop/capybara/visibility_option.rb +64 -0
  36. data/lib/rubocop/cop/capybara.rb +28 -0
  37. data/lib/rubocop/cop/capybara_cops.rb +4 -16
  38. data/lib/rubocop-capybara.rb +7 -2
  39. metadata +18 -4
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ # Enforces use of `Element#[]` instead of `native.attribute`.
7
+ #
8
+ # Only calls on Capybara finder chains are checked.
9
+ #
10
+ # @safety
11
+ # This cop's autocorrection is unsafe because attribute values may
12
+ # differ between the driver API and Capybara's `Element#[]`.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # find('.item').native.attribute(:id)
17
+ # page.find('.item').native.attribute('aria-label')
18
+ #
19
+ # # good
20
+ # find('.item')[:id]
21
+ # page.find('.item')['aria-label']
22
+ #
23
+ class NativeAttribute < RuboCop::Cop::Base
24
+ extend AutoCorrector
25
+ include CapybaraHelp
26
+
27
+ MSG = 'Use `Element#[]` instead of driver-specific `native.attribute`.'
28
+ RESTRICT_ON_SEND = %i[attribute].freeze
29
+
30
+ # @!method native_attribute(node)
31
+ def_node_matcher :native_attribute, <<~PATTERN
32
+ (call
33
+ $(call #element_finder? :native)
34
+ :attribute
35
+ $({str sym} _))
36
+ PATTERN
37
+
38
+ # @!method element_finder?(node)
39
+ def_node_matcher :element_finder?, <<~PATTERN
40
+ {#capybara_receiver?
41
+ (call #capybara_receiver?
42
+ {:ancestor :find :find_button :find_by_id
43
+ :find_field :find_link :first :sibling} ...)
44
+ (any_block #element_finder? ...)
45
+ (begin #element_finder?)}
46
+ PATTERN
47
+
48
+ def on_send(node)
49
+ native_attribute(node) do |native, attribute|
50
+ next unless native.receiver
51
+
52
+ add_offense(node) do |corrector|
53
+ next if attribute.str_type? && attribute.heredoc?
54
+
55
+ correct_attribute(corrector, node, native, attribute)
56
+ end
57
+ end
58
+ end
59
+ alias on_csend on_send
60
+
61
+ private
62
+
63
+ def correct_attribute(corrector, node, native, attribute)
64
+ range = node.source_range.with(begin_pos: native.loc.dot.begin_pos)
65
+ return if processed_source.comments.any? do |comment|
66
+ range.contains?(comment.source_range)
67
+ end
68
+
69
+ safe_navigation = native.csend_type? && node.csend_type?
70
+ template = safe_navigation ? '&.[](%s)' : '[%s]'
71
+ corrector.replace(range, format(template, attribute.source))
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -22,13 +22,13 @@ module RuboCop
22
22
  # end
23
23
  #
24
24
  # # good
25
- # within '#foo' do
25
+ # within :id, 'foo' do
26
26
  # # ...
27
27
  # end
28
28
  #
29
- class RedundantWithinFind < ::RuboCop::Cop::Base
30
- include CssSelector
29
+ class RedundantWithinFind < RuboCop::Cop::Base
31
30
  extend AutoCorrector
31
+
32
32
  MSG = 'Redundant `within %<method>s(...)` call detected.'
33
33
  RESTRICT_ON_SEND = %i[within].freeze
34
34
  FIND_METHODS = Set.new(%i[find find_by_id]).freeze
@@ -41,8 +41,10 @@ module RuboCop
41
41
 
42
42
  def on_send(node)
43
43
  within_find(node) do |find_node|
44
+ replacement = replaced(find_node)
45
+
44
46
  add_offense(find_node, message: msg(find_node)) do |corrector|
45
- corrector.replace(find_node, replaced(find_node))
47
+ corrector.replace(find_node, replacement) if replacement
46
48
  end
47
49
  end
48
50
  end
@@ -54,24 +56,11 @@ module RuboCop
54
56
  end
55
57
 
56
58
  def replaced(node)
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)
63
- else
64
- node.arguments.map(&:source).join(', ')
65
- .sub(/\A(["'])/, '\1#')
66
- end
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)
59
+ return unless node.first_argument
60
+ return if node.arguments.any?(&:block_pass_type?)
73
61
 
74
- ["#{quote}##{escaped_id}#{quote}", *rest_args].join(', ')
62
+ args = node.arguments.map(&:source).join(', ')
63
+ node.method?(:find_by_id) ? ":id, #{args}" : args
75
64
  end
76
65
  end
77
66
  end
@@ -25,9 +25,9 @@ module RuboCop
25
25
  # expect(page).to have_current_path('/callback', ignore_query: true)
26
26
  #
27
27
  # # bad (does not support autocorrection when `match` with a variable)
28
- # expect(page).to match(variable)
28
+ # expect(current_path).to match(variable)
29
29
  #
30
- class CurrentPathExpectation < ::RuboCop::Cop::Base
30
+ class CurrentPathExpectation < RuboCop::Cop::Base
31
31
  extend AutoCorrector
32
32
  include RangeHelp
33
33
 
@@ -79,8 +79,11 @@ module RuboCop
79
79
  end
80
80
 
81
81
  regexp_node_matcher(node.parent) do |to_sym, matcher_node, regexp|
82
+ regexp_expr = regexp_node_to_regexp_expr(regexp)
83
+ next unless regexp_expr
84
+
82
85
  rewrite_expectation(corrector, node, to_sym, matcher_node)
83
- convert_regexp_node_to_literal(corrector, matcher_node, regexp)
86
+ corrector.replace(matcher_node.first_argument, regexp_expr)
84
87
  end
85
88
  end
86
89
 
@@ -94,22 +97,19 @@ module RuboCop
94
97
  end
95
98
  corrector.replace(matcher_node.loc.selector, matcher_method)
96
99
  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)
100
+ add_ignore_query_options(corrector, node)
105
101
  end
106
102
 
107
103
  def regexp_node_to_regexp_expr(regexp_node)
108
104
  if regexp_node.xstr_type?
109
105
  "/\#{`#{regexp_node.value.value}`}/"
110
106
  else
107
+ return if regexp_node.value.match?(/#[{@$]/)
108
+
111
109
  Regexp.new(regexp_node.value).inspect
112
110
  end
111
+ rescue RegexpError
112
+ nil
113
113
  end
114
114
 
115
115
  def add_argument_parentheses(corrector, arg_node)
@@ -129,11 +129,8 @@ module RuboCop
129
129
 
130
130
  # `have_current_path` with no options will include the querystring
131
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
-
132
+ # This ensures the option `ignore_query: true` is added.
133
+ def add_ignore_query_options(corrector, node)
137
134
  expectation_node = node.parent.last_argument
138
135
  expectation_last_child = expectation_node.children.last
139
136
  corrector.insert_after(expectation_last_child,
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ module RSpec
7
+ # Checks expectations on `current_url` that bypass Capybara's waiting.
8
+ #
9
+ # `have_current_path` can match full URLs and waits for navigation.
10
+ # A regexp needs `url: true` so it matches the full URL, not just the
11
+ # path. Autocorrection is unsafe because URL parsing and waiting change
12
+ # comparison behavior.
13
+ # Dynamic `match` patterns and `include`, `start_with`, and `end_with`
14
+ # are reported without autocorrection.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # expect(page.current_url).to eq('https://example.com/login')
19
+ # expect(current_url).to match(%r{/login})
20
+ # expect(current_url).to match('login')
21
+ # expect(page.current_url).to include('/login')
22
+ #
23
+ # # good
24
+ # expect(page).to have_current_path('https://example.com/login')
25
+ # expect(page).to have_current_path(%r{/login}, url: true)
26
+ # expect(page).to have_current_path(/login/, url: true)
27
+ #
28
+ class CurrentUrlExpectation < RuboCop::Cop::Base
29
+ extend AutoCorrector
30
+
31
+ MSG = 'Use `have_current_path` on `page` to wait for the URL.'
32
+ RESTRICT_ON_SEND = %i[expect].freeze
33
+ PATH_MATCHERS = {
34
+ to: 'have_current_path',
35
+ not_to: 'have_no_current_path',
36
+ to_not: 'have_no_current_path'
37
+ }.freeze
38
+
39
+ # @!method url_actual(node)
40
+ def_node_matcher :url_actual, <<~PATTERN
41
+ (send nil? :expect
42
+ $(send {(send nil? :page) nil?} :current_url))
43
+ PATTERN
44
+
45
+ # @!method supported_matcher?(node)
46
+ def_node_matcher :supported_matcher?, <<~PATTERN
47
+ {(send nil? :eq _)
48
+ (send nil? :match _)
49
+ (send nil? {:include :start_with :end_with} (str _))}
50
+ PATTERN
51
+
52
+ # @!method expectation_runner?(node)
53
+ def_node_matcher :expectation_runner?, <<~PATTERN
54
+ (send (send nil? :expect _) {:to :not_to :to_not} _)
55
+ PATTERN
56
+
57
+ def self.autocorrect_incompatible_with
58
+ [Style::TrailingCommaInArguments]
59
+ end
60
+
61
+ def on_send(node)
62
+ actual = url_actual(node)
63
+ return unless actual
64
+
65
+ runner = node.parent
66
+ return unless expectation_runner?(runner)
67
+
68
+ matcher = runner.first_argument
69
+ return unless supported_matcher?(matcher)
70
+
71
+ register_offense(node.loc.selector, actual, runner, matcher)
72
+ end
73
+ alias on_csend on_send
74
+
75
+ private
76
+
77
+ def register_offense(range, actual, runner, matcher)
78
+ regexp = if matcher.method?(:match)
79
+ regexp_source(matcher.first_argument)
80
+ end
81
+ unless correctable_matcher?(matcher, regexp)
82
+ return add_offense(range)
83
+ end
84
+
85
+ add_offense(range) do |corrector|
86
+ autocorrect(corrector, actual, runner, matcher, regexp)
87
+ end
88
+ end
89
+
90
+ def correctable_matcher?(matcher, regexp)
91
+ return true if matcher.method?(:eq)
92
+ return false unless matcher.method?(:match)
93
+
94
+ matcher.first_argument.regexp_type? || regexp
95
+ end
96
+
97
+ def regexp_source(argument)
98
+ return unless argument.str_type?
99
+ return if argument.value.match?(/#[{@$]/)
100
+
101
+ Regexp.new(argument.value).inspect
102
+ rescue RegexpError
103
+ nil
104
+ end
105
+
106
+ def autocorrect(corrector, actual, runner, matcher, regexp)
107
+ corrector.replace(actual, 'page')
108
+ unless runner.method?(:to)
109
+ corrector.replace(runner.loc.selector, 'to')
110
+ end
111
+ corrector.replace(matcher.loc.selector,
112
+ PATH_MATCHERS.fetch(runner.method_name))
113
+ return unless matcher.method?(:match)
114
+
115
+ corrector.replace(matcher.first_argument, regexp) if regexp
116
+ corrector.insert_after(matcher.first_argument, ', url: true')
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ module RSpec
7
+ # Checks for expectations on the value of an element found by Capybara.
8
+ #
9
+ # @safety
10
+ # This cop's autocorrection is unsafe because `find('input').value`
11
+ # checks the value of the first matched element, while `have_field`
12
+ # checks that any field on the page has the expected value.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # expect(find('input').value).to eq('foobar')
17
+ # expect(find(:css, 'input').value).to eq('foobar')
18
+ # expect(find_field('Email').value).to eq('user@example.com')
19
+ #
20
+ # # good
21
+ # expect(page).to have_field(with: 'foobar')
22
+ # expect(page).to have_field('Email', with: 'user@example.com')
23
+ #
24
+ class FindElementValueEq < RuboCop::Cop::Base # rubocop:disable Metrics/ClassLength
25
+ extend AutoCorrector
26
+
27
+ MSG = 'Prefer `have_field` over checking `find(...).value`.'
28
+ RESTRICT_ON_SEND = %i[expect].freeze
29
+ FIELD_ELEMENT_SELECTORS = %w[input textarea].freeze
30
+
31
+ # @!method find_element_value_eq(node)
32
+ def_node_matcher :find_element_value_eq, <<~PATTERN
33
+ (send
34
+ (send nil? :expect (send $_ :value))
35
+ ${:to :to_not :not_to}
36
+ $(send nil? :eq $_)
37
+ )
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ find_element_value_eq(node.parent) do |finder, to_symbol,
42
+ matcher, expected|
43
+ register_offense(node, finder, to_symbol, matcher, expected)
44
+ end
45
+ end
46
+ alias on_csend on_send
47
+
48
+ private
49
+
50
+ def register_offense(node, finder, to_symbol, matcher, expected)
51
+ replacement = replacement_for_finder(finder)
52
+ return unless replacement
53
+
54
+ expect_target, field_locator = replacement
55
+ matcher_replacement =
56
+ replacement_matcher(to_symbol, expected, field_locator)
57
+
58
+ return add_offense(node.loc.selector) unless matcher_replacement
59
+
60
+ add_offense(node.loc.selector) do |corrector|
61
+ autocorrect(corrector, node, matcher, expect_target,
62
+ matcher_replacement)
63
+ end
64
+ end
65
+
66
+ def replacement_for_finder(node)
67
+ return unless node&.send_type?
68
+
69
+ case node.method_name
70
+ when :find
71
+ replacement_for_find(node)
72
+ when :find_field
73
+ replacement_for_find_field(node)
74
+ end
75
+ end
76
+
77
+ def replacement_for_find(node)
78
+ if (field_locator = field_selector_locator(node.arguments))
79
+ return [expect_target(node), field_locator]
80
+ end
81
+
82
+ return unless field_element_selector?(node.arguments)
83
+
84
+ [expect_target(node), nil]
85
+ end
86
+
87
+ def replacement_for_find_field(node)
88
+ return unless node.arguments.one?
89
+
90
+ [expect_target(node), node.first_argument]
91
+ end
92
+
93
+ def field_element_selector?(arguments)
94
+ return field_element_name?(arguments.first) if arguments.one?
95
+
96
+ arguments.length == 2 &&
97
+ arguments.first.sym_type? &&
98
+ arguments.first.value == :css &&
99
+ field_element_name?(arguments.last)
100
+ end
101
+
102
+ def field_selector_locator(arguments)
103
+ return unless arguments.length == 2
104
+
105
+ selector_type, locator = arguments
106
+ return unless field_selector?(selector_type)
107
+
108
+ locator
109
+ end
110
+
111
+ def field_selector?(node)
112
+ node.sym_type? && node.value == :field
113
+ end
114
+
115
+ def field_element_name?(node)
116
+ node.str_type? && FIELD_ELEMENT_SELECTORS.include?(node.value)
117
+ end
118
+
119
+ def expect_target(node)
120
+ node.receiver&.source || 'page'
121
+ end
122
+
123
+ def autocorrect(corrector, node, matcher, expect_target,
124
+ matcher_replacement)
125
+ corrector.replace(node.first_argument, expect_target)
126
+ corrector.replace(node.parent.loc.selector, 'to')
127
+ corrector.replace(matcher, matcher_replacement)
128
+ end
129
+
130
+ def replacement_matcher(to_symbol, expected, field_locator)
131
+ expected_value = replacement_value(expected)
132
+ return unless expected_value
133
+
134
+ matcher = to_symbol == :to ? 'have_field' : 'have_no_field'
135
+ args = ["with: #{expected_value}"]
136
+ args.unshift(replacement_locator(field_locator)) if field_locator
137
+
138
+ "#{matcher}(#{args.join(', ')})"
139
+ end
140
+
141
+ def replacement_locator(locator)
142
+ return locator.source if locator.hash_type? && !locator.braces?
143
+
144
+ replacement_value(locator)
145
+ end
146
+
147
+ def replacement_value(expected)
148
+ return if unsafe_expected_value?(expected)
149
+
150
+ if expected.hash_type? && !expected.braces?
151
+ return "{#{expected.source}}"
152
+ end
153
+
154
+ if method_call_with_no_parentheses?(expected)
155
+ return "(#{expected.source})"
156
+ end
157
+
158
+ expected.source
159
+ end
160
+
161
+ def unsafe_expected_value?(node)
162
+ node.splat_type? ||
163
+ (node.hash_type? && node.children.any?(&:kwsplat_type?))
164
+ end
165
+
166
+ def method_call_with_no_parentheses?(node)
167
+ node.send_type? && node.arguments? && !node.parenthesized?
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -19,7 +19,7 @@ module RuboCop
19
19
  # expect(page).to have_text('capy')
20
20
  # expect(page).to have_no_text('bara')
21
21
  #
22
- class HaveContent < ::RuboCop::Cop::Base
22
+ class HaveContent < RuboCop::Cop::Base
23
23
  extend AutoCorrector
24
24
 
25
25
  MSG = 'Prefer `%<good>s` over `%<bad>s`.'
@@ -4,20 +4,24 @@ module RuboCop
4
4
  module Cop
5
5
  module Capybara
6
6
  module RSpec
7
- # Use `have_css` or `have_xpath` instead of `have_selector`.
7
+ # Use CSS or XPath matchers instead of generic selector matchers.
8
8
  #
9
9
  # @example
10
10
  # # bad
11
11
  # expect(foo).to have_selector(:css, 'bar')
12
+ # expect(foo).to have_no_selector(:css, 'bar')
12
13
  #
13
14
  # # good
14
15
  # expect(foo).to have_css('bar')
16
+ # expect(foo).to have_no_css('bar')
15
17
  #
16
18
  # # bad
17
19
  # expect(foo).to have_selector(:xpath, 'bar')
20
+ # expect(foo).to have_no_selector(:xpath, 'bar')
18
21
  #
19
22
  # # good
20
23
  # expect(foo).to have_xpath('bar')
24
+ # expect(foo).to have_no_xpath('bar')
21
25
  #
22
26
  # @example DefaultSelector: css (default)
23
27
  # # bad
@@ -33,12 +37,12 @@ module RuboCop
33
37
  # # good
34
38
  # expect(foo).to have_xpath('bar')
35
39
  #
36
- class HaveSelector < ::RuboCop::Cop::Base
40
+ class HaveSelector < RuboCop::Cop::Base
37
41
  extend AutoCorrector
38
42
  include RangeHelp
39
43
 
40
- MSG = 'Use `%<good>s` instead of `have_selector`.'
41
- RESTRICT_ON_SEND = %i[have_selector].freeze
44
+ MSG = 'Use `%<good>s` instead of `%<bad>s`.'
45
+ RESTRICT_ON_SEND = %i[have_selector have_no_selector].freeze
42
46
  SELECTORS = %i[css xpath].freeze
43
47
 
44
48
  def on_send(node)
@@ -54,33 +58,40 @@ module RuboCop
54
58
  return unless SELECTORS.include?(type.value)
55
59
  return unless (locator = node.arguments[1])
56
60
 
57
- add_offense(node, message: message_typed(type)) do |corrector|
61
+ replacement = replacement(node, type.value)
62
+ add_offense(node,
63
+ message: message(node, replacement)) do |corrector|
58
64
  corrector.remove(deletion_range(type, locator))
59
- corrector.replace(node.loc.selector, "have_#{type.value}")
65
+ corrector.replace(node.loc.selector, replacement)
60
66
  end
61
67
  end
62
68
 
63
- def message_typed(type)
64
- format(MSG, good: "have_#{type.value}")
65
- end
66
-
67
69
  def deletion_range(first_argument, second_argument)
68
70
  range_between(first_argument.source_range.begin_pos,
69
71
  second_argument.source_range.begin_pos)
70
72
  end
71
73
 
72
74
  def on_select_without_type(node)
73
- add_offense(node, message: message_untyped) do |corrector|
74
- corrector.replace(node.loc.selector, "have_#{default_selector}")
75
+ return unless (selector = default_selector)
76
+
77
+ replacement = replacement(node, selector)
78
+ add_offense(node,
79
+ message: message(node, replacement)) do |corrector|
80
+ corrector.replace(node.loc.selector, replacement)
75
81
  end
76
82
  end
77
83
 
78
- def message_untyped
79
- format(MSG, good: "have_#{default_selector}")
84
+ def replacement(node, selector)
85
+ node.method_name.to_s.sub('selector', selector.to_s)
86
+ end
87
+
88
+ def message(node, replacement)
89
+ format(MSG, good: replacement, bad: node.method_name)
80
90
  end
81
91
 
82
92
  def default_selector
83
- cop_config.fetch('DefaultSelector', 'css')
93
+ selector = cop_config['DefaultSelector'].to_s.to_sym
94
+ selector if SELECTORS.include?(selector)
84
95
  end
85
96
  end
86
97
  end
@@ -22,7 +22,7 @@ module RuboCop
22
22
  # # good
23
23
  # expect(page).to match_style(display: 'block')
24
24
  #
25
- class MatchStyle < ::RuboCop::Cop::Base
25
+ class MatchStyle < RuboCop::Cop::Base
26
26
  extend AutoCorrector
27
27
 
28
28
  MSG = 'Use `%<good>s` instead of `%<bad>s`.'
@@ -24,7 +24,7 @@ module RuboCop
24
24
  # expect(page).not_to have_selector 'a'
25
25
  # expect(page).not_to have_css('a')
26
26
  #
27
- class NegationMatcher < ::RuboCop::Cop::Base
27
+ class NegationMatcher < RuboCop::Cop::Base
28
28
  extend AutoCorrector
29
29
  include ConfigurableEnforcedStyle
30
30
  include CapybaraHelp
@@ -27,7 +27,7 @@ module RuboCop
27
27
  # expect(page).to have_css('a')
28
28
  # expect(page).not_to have_link('bar')
29
29
  #
30
- class NegationMatcherAfterVisit < ::RuboCop::Cop::Base
30
+ class NegationMatcherAfterVisit < RuboCop::Cop::Base
31
31
  include CapybaraHelp
32
32
 
33
33
  MSG = 'Do not use negation matcher immediately after visit.'
@@ -37,7 +37,7 @@ module RuboCop
37
37
  def_node_matcher :negation_matcher?, <<~PATTERN
38
38
  {
39
39
  (send (send nil? :expect _) :to (send nil? %NEGATIVE_MATCHERS ...))
40
- (send (send nil? :expect _) :not_to (send nil? %POSITIVE_MATCHERS ...))
40
+ (send (send nil? :expect _) {:not_to :to_not} (send nil? %POSITIVE_MATCHERS ...))
41
41
  }
42
42
  PATTERN
43
43