rubocop-rspec 2.16.0 → 2.24.1
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 +124 -9
- data/README.md +3 -3
- data/config/default.yml +145 -18
- data/config/obsoletion.yml +15 -0
- data/lib/rubocop/cop/rspec/be_empty.rb +44 -0
- data/lib/rubocop/cop/rspec/be_nil.rb +2 -2
- data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +29 -115
- data/lib/rubocop/cop/rspec/capybara/match_style.rb +38 -0
- 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/change_by_zero.rb +33 -23
- data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
- data/lib/rubocop/cop/rspec/context_method.rb +5 -1
- data/lib/rubocop/cop/rspec/context_wording.rb +13 -6
- data/lib/rubocop/cop/rspec/describe_method.rb +16 -8
- data/lib/rubocop/cop/rspec/described_class.rb +2 -1
- data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +7 -5
- data/lib/rubocop/cop/rspec/dialect.rb +1 -1
- data/lib/rubocop/cop/rspec/duplicated_metadata.rb +2 -2
- data/lib/rubocop/cop/rspec/empty_example_group.rb +10 -7
- data/lib/rubocop/cop/rspec/empty_hook.rb +2 -2
- data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
- data/lib/rubocop/cop/rspec/eq.rb +47 -0
- data/lib/rubocop/cop/rspec/example_wording.rb +1 -1
- data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +14 -5
- data/lib/rubocop/cop/rspec/expect_actual.rb +4 -4
- data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -1
- data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +25 -118
- data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +40 -107
- data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +30 -250
- data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +19 -46
- data/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb +23 -64
- data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +45 -79
- data/lib/rubocop/cop/rspec/file_path.rb +8 -2
- data/lib/rubocop/cop/rspec/focus.rb +19 -5
- data/lib/rubocop/cop/rspec/hook_argument.rb +12 -9
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +5 -3
- data/lib/rubocop/cop/rspec/indexed_let.rb +112 -0
- data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
- data/lib/rubocop/cop/rspec/let_before_examples.rb +8 -4
- data/lib/rubocop/cop/rspec/let_setup.rb +6 -8
- data/lib/rubocop/cop/rspec/match_array.rb +59 -0
- data/lib/rubocop/cop/rspec/metadata_style.rb +197 -0
- data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +1 -2
- data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
- data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
- data/lib/rubocop/cop/rspec/mixin/metadata.rb +21 -7
- data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +20 -4
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -1
- data/lib/rubocop/cop/rspec/named_subject.rb +7 -5
- data/lib/rubocop/cop/rspec/no_expectation_example.rb +2 -5
- data/lib/rubocop/cop/rspec/overwriting_setup.rb +3 -1
- data/lib/rubocop/cop/rspec/pending.rb +23 -13
- data/lib/rubocop/cop/rspec/pending_without_reason.rb +72 -36
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +49 -40
- data/lib/rubocop/cop/rspec/rails/have_http_status.rb +11 -6
- data/lib/rubocop/cop/rspec/rails/http_status.rb +107 -34
- data/lib/rubocop/cop/rspec/rails/inferred_spec_type.rb +4 -4
- data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +60 -0
- data/lib/rubocop/cop/rspec/rails/negation_be_valid.rb +102 -0
- data/lib/rubocop/cop/rspec/rails/travel_around.rb +92 -0
- data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
- data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
- data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +3 -6
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +3 -6
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +3 -4
- data/lib/rubocop/cop/rspec/scattered_setup.rb +23 -6
- data/lib/rubocop/cop/rspec/shared_context.rb +12 -13
- data/lib/rubocop/cop/rspec/shared_examples.rb +6 -4
- data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
- data/lib/rubocop/cop/rspec/sort_metadata.rb +4 -3
- data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
- data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
- data/lib/rubocop/cop/rspec/stubbed_mock.rb +1 -1
- data/lib/rubocop/cop/rspec/subject_stub.rb +0 -1
- data/lib/rubocop/cop/rspec/variable_definition.rb +5 -2
- data/lib/rubocop/cop/rspec/variable_name.rb +4 -1
- data/lib/rubocop/cop/rspec/verified_double_reference.rb +7 -7
- data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
- data/lib/rubocop/cop/rspec/void_expect.rb +2 -1
- data/lib/rubocop/cop/rspec_cops.rb +16 -0
- data/lib/rubocop/rspec/config_formatter.rb +16 -0
- data/lib/rubocop/rspec/example_group.rb +6 -8
- data/lib/rubocop/rspec/language/node_pattern.rb +26 -0
- data/lib/rubocop/rspec/language.rb +25 -16
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop-rspec.rb +4 -5
- metadata +50 -8
- data/lib/rubocop/cop/rspec/mixin/capybara_help.rb +0 -80
- data/lib/rubocop/cop/rspec/mixin/css_selector.rb +0 -146
- data/lib/rubocop/rspec/factory_bot/language.rb +0 -37
- data/lib/rubocop/rspec/factory_bot.rb +0 -64
@@ -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
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module Capybara
|
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
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
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
|