rubocop-rspec 2.13.1 → 2.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +405 -379
- data/CODE_OF_CONDUCT.md +4 -4
- data/MIT-LICENSE.md +1 -2
- data/config/default.yml +65 -4
- data/lib/rubocop/cop/rspec/capybara/negation_matcher.rb +106 -0
- data/lib/rubocop/cop/rspec/capybara/specific_actions.rb +85 -0
- data/lib/rubocop/cop/rspec/capybara/specific_finders.rb +9 -2
- data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +5 -82
- data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +14 -14
- data/lib/rubocop/cop/rspec/change_by_zero.rb +1 -1
- data/lib/rubocop/cop/rspec/context_wording.rb +4 -2
- data/lib/rubocop/cop/rspec/example_wording.rb +32 -0
- data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +99 -0
- data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +2 -2
- data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +1 -19
- data/lib/rubocop/cop/rspec/implicit_subject.rb +86 -19
- data/lib/rubocop/cop/rspec/let_before_examples.rb +15 -1
- data/lib/rubocop/cop/rspec/mixin/capybara_help.rb +80 -0
- data/lib/rubocop/cop/rspec/mixin/css_selector.rb +48 -1
- data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +23 -0
- data/lib/rubocop/cop/rspec/no_expectation_example.rb +47 -6
- data/lib/rubocop/cop/rspec/pending.rb +2 -11
- data/lib/rubocop/cop/rspec/rails/inferred_spec_type.rb +135 -0
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +1 -1
- data/lib/rubocop/cop/rspec/sort_metadata.rb +102 -0
- data/lib/rubocop/cop/rspec/subject_declaration.rb +1 -1
- data/lib/rubocop/cop/rspec_cops.rb +5 -0
- data/lib/rubocop/rspec/factory_bot/language.rb +20 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop-rspec.rb +2 -0
- metadata +9 -2
data/CODE_OF_CONDUCT.md
CHANGED
@@ -9,9 +9,9 @@ to the RuboCop community. It applies to all "collaborative spaces", which are
|
|
9
9
|
defined as community communications channels (such as mailing lists, submitted
|
10
10
|
patches, commit comments, etc.).
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
- Participants will be tolerant of opposing views.
|
13
|
+
- Participants must ensure that their language and actions are free of personal
|
14
14
|
attacks and disparaging personal remarks.
|
15
|
-
|
15
|
+
- When interpreting the words and actions of others, participants should always
|
16
16
|
assume good intentions.
|
17
|
-
|
17
|
+
- Behaviour which can be reasonably considered harassment will not be tolerated.
|
data/MIT-LICENSE.md
CHANGED
data/config/default.yml
CHANGED
@@ -194,7 +194,7 @@ RSpec/ChangeByZero:
|
|
194
194
|
Description: Prefer negated matchers over `to change.by(0)`.
|
195
195
|
Enabled: pending
|
196
196
|
VersionAdded: '2.11'
|
197
|
-
VersionChanged: '2.
|
197
|
+
VersionChanged: '2.14'
|
198
198
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ChangeByZero
|
199
199
|
NegatedMatcher: ~
|
200
200
|
|
@@ -383,8 +383,10 @@ RSpec/ExampleWording:
|
|
383
383
|
have: has
|
384
384
|
HAVE: HAS
|
385
385
|
IgnoredWords: []
|
386
|
+
DisallowedExamples:
|
387
|
+
- works
|
386
388
|
VersionAdded: '1.0'
|
387
|
-
VersionChanged: '
|
389
|
+
VersionChanged: '2.13'
|
388
390
|
StyleGuide: https://rspec.rubystyle.guide/#should-in-example-docstrings
|
389
391
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWording
|
390
392
|
|
@@ -492,8 +494,9 @@ RSpec/ImplicitSubject:
|
|
492
494
|
- single_line_only
|
493
495
|
- single_statement_only
|
494
496
|
- disallow
|
497
|
+
- require_implicit
|
495
498
|
VersionAdded: '1.29'
|
496
|
-
VersionChanged: '
|
499
|
+
VersionChanged: '2.13'
|
497
500
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject
|
498
501
|
|
499
502
|
RSpec/InstanceSpy:
|
@@ -640,7 +643,11 @@ RSpec/NoExpectationExample:
|
|
640
643
|
Enabled: pending
|
641
644
|
Safe: false
|
642
645
|
VersionAdded: '2.13'
|
646
|
+
VersionChanged: '2.14'
|
643
647
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample
|
648
|
+
AllowedPatterns:
|
649
|
+
- "^expect_"
|
650
|
+
- "^assert_"
|
644
651
|
|
645
652
|
RSpec/NotToNot:
|
646
653
|
Description: Checks for consistent method usage for negating expectations.
|
@@ -763,6 +770,12 @@ RSpec/SingleArgumentMessageChain:
|
|
763
770
|
VersionChanged: '1.10'
|
764
771
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SingleArgumentMessageChain
|
765
772
|
|
773
|
+
RSpec/SortMetadata:
|
774
|
+
Description: Sort RSpec metadata alphabetically.
|
775
|
+
Enabled: pending
|
776
|
+
VersionAdded: '2.14'
|
777
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SortMetadata
|
778
|
+
|
766
779
|
RSpec/StubbedMock:
|
767
780
|
Description: Checks that message expectations do not have a configured response.
|
768
781
|
Enabled: true
|
@@ -807,7 +820,6 @@ RSpec/VariableName:
|
|
807
820
|
- snake_case
|
808
821
|
- camelCase
|
809
822
|
AllowedPatterns: []
|
810
|
-
IgnoredPatterns: []
|
811
823
|
VersionAdded: '1.40'
|
812
824
|
VersionChanged: '2.13'
|
813
825
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableName
|
@@ -866,6 +878,22 @@ RSpec/Capybara/FeatureMethods:
|
|
866
878
|
VersionChanged: '2.0'
|
867
879
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/FeatureMethods
|
868
880
|
|
881
|
+
RSpec/Capybara/NegationMatcher:
|
882
|
+
Description: Enforces use of `have_no_*` or `not_to` for negated expectations.
|
883
|
+
Enabled: pending
|
884
|
+
VersionAdded: '2.14'
|
885
|
+
EnforcedStyle: not_to
|
886
|
+
SupportedStyles:
|
887
|
+
- have_no
|
888
|
+
- not_to
|
889
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/NegationMatcher
|
890
|
+
|
891
|
+
RSpec/Capybara/SpecificActions:
|
892
|
+
Description: Checks for there is a more specific actions offered by Capybara.
|
893
|
+
Enabled: pending
|
894
|
+
VersionAdded: '2.14'
|
895
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/SpecificActions
|
896
|
+
|
869
897
|
RSpec/Capybara/SpecificFinders:
|
870
898
|
Description: Checks if there is a more specific finder offered by Capybara.
|
871
899
|
Enabled: pending
|
@@ -901,6 +929,16 @@ RSpec/FactoryBot/AttributeDefinedStatically:
|
|
901
929
|
VersionChanged: '2.0'
|
902
930
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically
|
903
931
|
|
932
|
+
RSpec/FactoryBot/ConsistentParenthesesStyle:
|
933
|
+
Description: Use a consistent style for parentheses in factory bot calls.
|
934
|
+
Enabled: pending
|
935
|
+
EnforcedStyle: require_parentheses
|
936
|
+
SupportedStyles:
|
937
|
+
- require_parentheses
|
938
|
+
- omit_parentheses
|
939
|
+
VersionAdded: '2.14'
|
940
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/ConsistentParenthesesStyle
|
941
|
+
|
904
942
|
RSpec/FactoryBot/CreateList:
|
905
943
|
Description: Checks for create_list usage.
|
906
944
|
Enabled: true
|
@@ -954,6 +992,29 @@ RSpec/Rails/HaveHttpStatus:
|
|
954
992
|
VersionAdded: '2.12'
|
955
993
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/HaveHttpStatus
|
956
994
|
|
995
|
+
RSpec/Rails/InferredSpecType:
|
996
|
+
Description: Identifies redundant spec type.
|
997
|
+
Enabled: pending
|
998
|
+
Safe: false
|
999
|
+
VersionAdded: '2.14'
|
1000
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/InferredSpecType
|
1001
|
+
Inferences:
|
1002
|
+
channels: channel
|
1003
|
+
controllers: controller
|
1004
|
+
features: feature
|
1005
|
+
generator: generator
|
1006
|
+
helpers: helper
|
1007
|
+
jobs: job
|
1008
|
+
mailboxes: mailbox
|
1009
|
+
mailers: mailer
|
1010
|
+
models: model
|
1011
|
+
requests: request
|
1012
|
+
integration: request
|
1013
|
+
api: request
|
1014
|
+
routing: routing
|
1015
|
+
system: system
|
1016
|
+
views: view
|
1017
|
+
|
957
1018
|
RSpec/Rails/HttpStatus:
|
958
1019
|
Description: Enforces use of symbolic or numeric value to describe HTTP status.
|
959
1020
|
Enabled: true
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module Capybara
|
7
|
+
# Enforces use of `have_no_*` or `not_to` for negated expectations.
|
8
|
+
#
|
9
|
+
# @example EnforcedStyle: not_to (default)
|
10
|
+
# # bad
|
11
|
+
# expect(page).to have_no_selector
|
12
|
+
# expect(page).to have_no_css('a')
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# expect(page).not_to have_selector
|
16
|
+
# expect(page).not_to have_css('a')
|
17
|
+
#
|
18
|
+
# @example EnforcedStyle: have_no
|
19
|
+
# # bad
|
20
|
+
# expect(page).not_to have_selector
|
21
|
+
# expect(page).not_to have_css('a')
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# expect(page).to have_no_selector
|
25
|
+
# expect(page).to have_no_css('a')
|
26
|
+
#
|
27
|
+
class NegationMatcher < Base
|
28
|
+
extend AutoCorrector
|
29
|
+
include ConfigurableEnforcedStyle
|
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
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module Capybara
|
7
|
+
# Checks for there is a more specific actions offered by Capybara.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# find('a').click
|
13
|
+
# find('button.cls').click
|
14
|
+
# find('a', exact_text: 'foo').click
|
15
|
+
# find('div button').click
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# click_link
|
19
|
+
# click_button(class: 'cls')
|
20
|
+
# click_link(exact_text: 'foo')
|
21
|
+
# find('div').click_button
|
22
|
+
#
|
23
|
+
class SpecificActions < Base
|
24
|
+
MSG = "Prefer `%<good_action>s` over `find('%<selector>s').click`."
|
25
|
+
RESTRICT_ON_SEND = %i[click].freeze
|
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
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -76,8 +76,15 @@ module RuboCop
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def offense_range(node)
|
79
|
-
range_between(node.loc.selector.begin_pos,
|
80
|
-
|
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
|
81
88
|
end
|
82
89
|
end
|
83
90
|
end
|
@@ -26,7 +26,9 @@ module RuboCop
|
|
26
26
|
# expect(page).to have_select
|
27
27
|
# expect(page).to have_field('foo')
|
28
28
|
#
|
29
|
-
class SpecificMatcher < Base
|
29
|
+
class SpecificMatcher < Base
|
30
|
+
include CapybaraHelp
|
31
|
+
|
30
32
|
MSG = 'Prefer `%<good_matcher>s` over `%<bad_matcher>s`.'
|
31
33
|
RESTRICT_ON_SEND = %i[have_selector have_no_selector have_css
|
32
34
|
have_no_css].freeze
|
@@ -37,51 +39,18 @@ module RuboCop
|
|
37
39
|
'select' => 'select',
|
38
40
|
'input' => 'field'
|
39
41
|
}.freeze
|
40
|
-
SPECIFIC_MATCHER_OPTIONS = {
|
41
|
-
'button' => (
|
42
|
-
CssSelector::COMMON_OPTIONS + %w[disabled name value title type]
|
43
|
-
).freeze,
|
44
|
-
'link' => (
|
45
|
-
CssSelector::COMMON_OPTIONS + %w[href alt title download]
|
46
|
-
).freeze,
|
47
|
-
'table' => (
|
48
|
-
CssSelector::COMMON_OPTIONS + %w[
|
49
|
-
caption with_cols cols with_rows rows
|
50
|
-
]
|
51
|
-
).freeze,
|
52
|
-
'select' => (
|
53
|
-
CssSelector::COMMON_OPTIONS + %w[
|
54
|
-
disabled name placeholder options enabled_options
|
55
|
-
disabled_options selected with_selected multiple with_options
|
56
|
-
]
|
57
|
-
).freeze,
|
58
|
-
'field' => (
|
59
|
-
CssSelector::COMMON_OPTIONS + %w[
|
60
|
-
checked unchecked disabled valid name placeholder
|
61
|
-
validation_message readonly with type multiple
|
62
|
-
]
|
63
|
-
).freeze
|
64
|
-
}.freeze
|
65
|
-
SPECIFIC_MATCHER_PSEUDO_CLASSES = %w[
|
66
|
-
not() disabled enabled checked unchecked
|
67
|
-
].freeze
|
68
42
|
|
69
43
|
# @!method first_argument(node)
|
70
44
|
def_node_matcher :first_argument, <<-PATTERN
|
71
45
|
(send nil? _ (str $_) ... )
|
72
46
|
PATTERN
|
73
47
|
|
74
|
-
# @!method option?(node)
|
75
|
-
def_node_search :option?, <<-PATTERN
|
76
|
-
(pair (sym %) _)
|
77
|
-
PATTERN
|
78
|
-
|
79
48
|
def on_send(node)
|
80
49
|
first_argument(node) do |arg|
|
81
50
|
next unless (matcher = specific_matcher(arg))
|
82
51
|
next if CssSelector.multiple_selectors?(arg)
|
83
|
-
next unless
|
84
|
-
next unless
|
52
|
+
next unless specific_option?(node, arg, matcher)
|
53
|
+
next unless specific_pseudo_classes?(arg)
|
85
54
|
|
86
55
|
add_offense(node, message: message(node, matcher))
|
87
56
|
end
|
@@ -94,52 +63,6 @@ module RuboCop
|
|
94
63
|
SPECIFIC_MATCHER[splitted_arg]
|
95
64
|
end
|
96
65
|
|
97
|
-
def specific_matcher_option?(node, arg, matcher)
|
98
|
-
attrs = CssSelector.attributes(arg).keys
|
99
|
-
return true if attrs.empty?
|
100
|
-
return false unless replaceable_matcher?(node, matcher, attrs)
|
101
|
-
|
102
|
-
attrs.all? do |attr|
|
103
|
-
SPECIFIC_MATCHER_OPTIONS.fetch(matcher, []).include?(attr)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def specific_matcher_pseudo_classes?(arg)
|
108
|
-
CssSelector.pseudo_classes(arg).all? do |pseudo_class|
|
109
|
-
replaceable_pseudo_class?(pseudo_class, arg)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def replaceable_pseudo_class?(pseudo_class, arg)
|
114
|
-
unless SPECIFIC_MATCHER_PSEUDO_CLASSES.include?(pseudo_class)
|
115
|
-
return false
|
116
|
-
end
|
117
|
-
|
118
|
-
case pseudo_class
|
119
|
-
when 'not()' then replaceable_pseudo_class_not?(arg)
|
120
|
-
else true
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def replaceable_pseudo_class_not?(arg)
|
125
|
-
arg.scan(/not\(.*?\)/).all? do |not_arg|
|
126
|
-
CssSelector.attributes(not_arg).values.all? do |v|
|
127
|
-
v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def replaceable_matcher?(node, matcher, attrs)
|
133
|
-
case matcher
|
134
|
-
when 'link' then replaceable_to_have_link?(node, attrs)
|
135
|
-
else true
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def replaceable_to_have_link?(node, attrs)
|
140
|
-
option?(node, :href) || attrs.include?('href')
|
141
|
-
end
|
142
|
-
|
143
66
|
def message(node, matcher)
|
144
67
|
format(MSG,
|
145
68
|
good_matcher: good_matcher(node, matcher),
|
@@ -29,20 +29,20 @@ module RuboCop
|
|
29
29
|
class VisibilityMatcher < Base
|
30
30
|
MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
|
31
31
|
MSG_TRUE = 'Use `:visible` instead of `true`.'
|
32
|
-
CAPYBARA_MATCHER_METHODS = %
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
32
|
+
CAPYBARA_MATCHER_METHODS = %w[
|
33
|
+
button
|
34
|
+
checked_field
|
35
|
+
css
|
36
|
+
field
|
37
|
+
link
|
38
|
+
select
|
39
|
+
selector
|
40
|
+
table
|
41
|
+
unchecked_field
|
42
|
+
xpath
|
43
|
+
].flat_map do |element|
|
44
|
+
["have_#{element}".to_sym, "have_no_#{element}".to_sym]
|
45
|
+
end
|
46
46
|
|
47
47
|
RESTRICT_ON_SEND = CAPYBARA_MATCHER_METHODS
|
48
48
|
|
@@ -61,7 +61,7 @@ module RuboCop
|
|
61
61
|
extend AutoCorrector
|
62
62
|
MSG = 'Prefer `not_to change` over `to change.by(0)`.'
|
63
63
|
MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
|
64
|
-
|
64
|
+
'over `change.by(0)`.'
|
65
65
|
RESTRICT_ON_SEND = %i[change].freeze
|
66
66
|
|
67
67
|
# @!method expect_change_with_arguments(node)
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
# # .rubocop.yml
|
44
44
|
# # RSpec/ContextWording:
|
45
45
|
# # AllowedPatterns:
|
46
|
-
# # -
|
46
|
+
# # - とき$
|
47
47
|
#
|
48
48
|
# @example
|
49
49
|
# # bad
|
@@ -92,7 +92,9 @@ module RuboCop
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def expect_patterns
|
95
|
-
inspected = allowed_patterns.map
|
95
|
+
inspected = allowed_patterns.map do |pattern|
|
96
|
+
pattern.inspect.gsub(/\A"|"\z/, '/')
|
97
|
+
end
|
96
98
|
return inspected.first if inspected.size == 1
|
97
99
|
|
98
100
|
inspected << "or #{inspected.pop}"
|
@@ -6,12 +6,17 @@ module RuboCop
|
|
6
6
|
# Checks for common mistakes in example descriptions.
|
7
7
|
#
|
8
8
|
# This cop will correct docstrings that begin with 'should' and 'it'.
|
9
|
+
# This cop will also look for insufficient examples and call them out.
|
9
10
|
#
|
10
11
|
# @see http://betterspecs.org/#should
|
11
12
|
#
|
12
13
|
# The autocorrect is experimental - use with care! It can be configured
|
13
14
|
# with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only).
|
14
15
|
#
|
16
|
+
# Use the DisallowedExamples setting to prevent unclear or insufficient
|
17
|
+
# descriptions. Please note that this config will not be treated as
|
18
|
+
# case sensitive.
|
19
|
+
#
|
15
20
|
# @example
|
16
21
|
# # bad
|
17
22
|
# it 'should find nothing' do
|
@@ -30,11 +35,21 @@ module RuboCop
|
|
30
35
|
# it 'does things' do
|
31
36
|
# end
|
32
37
|
#
|
38
|
+
# @example `DisallowedExamples: ['works']` (default)
|
39
|
+
# # bad
|
40
|
+
# it 'works' do
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# # good
|
44
|
+
# it 'marks the task as done' do
|
45
|
+
# end
|
33
46
|
class ExampleWording < Base
|
34
47
|
extend AutoCorrector
|
35
48
|
|
36
49
|
MSG_SHOULD = 'Do not use should when describing your tests.'
|
37
50
|
MSG_IT = "Do not repeat 'it' when describing your tests."
|
51
|
+
MSG_INSUFFICIENT_DESCRIPTION = 'Your example description is ' \
|
52
|
+
'insufficient.'
|
38
53
|
|
39
54
|
SHOULD_PREFIX = /\Ashould(?:n't)?\b/i.freeze
|
40
55
|
IT_PREFIX = /\Ait /i.freeze
|
@@ -53,12 +68,20 @@ module RuboCop
|
|
53
68
|
add_wording_offense(description_node, MSG_SHOULD)
|
54
69
|
elsif message.match?(IT_PREFIX)
|
55
70
|
add_wording_offense(description_node, MSG_IT)
|
71
|
+
else
|
72
|
+
check_and_handle_insufficient_examples(description_node)
|
56
73
|
end
|
57
74
|
end
|
58
75
|
end
|
59
76
|
|
60
77
|
private
|
61
78
|
|
79
|
+
def check_and_handle_insufficient_examples(description)
|
80
|
+
if insufficient_examples.include?(preprocess(text(description)))
|
81
|
+
add_wording_offense(description, MSG_INSUFFICIENT_DESCRIPTION)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
62
85
|
def add_wording_offense(node, message)
|
63
86
|
docstring = docstring(node)
|
64
87
|
|
@@ -113,6 +136,15 @@ module RuboCop
|
|
113
136
|
def ignored_words
|
114
137
|
cop_config.fetch('IgnoredWords', [])
|
115
138
|
end
|
139
|
+
|
140
|
+
def insufficient_examples
|
141
|
+
examples = cop_config.fetch('DisallowedExamples', [])
|
142
|
+
examples.map! { |example| preprocess(example) }
|
143
|
+
end
|
144
|
+
|
145
|
+
def preprocess(message)
|
146
|
+
message.strip.squeeze(' ').downcase
|
147
|
+
end
|
116
148
|
end
|
117
149
|
end
|
118
150
|
end
|