rubocop-rspec 2.12.1 → 2.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +114 -86
  3. data/config/default.yml +44 -6
  4. data/config/obsoletion.yml +14 -0
  5. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +8 -9
  6. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +8 -9
  7. data/lib/rubocop/cop/rspec/any_instance.rb +1 -0
  8. data/lib/rubocop/cop/rspec/around_block.rb +26 -3
  9. data/lib/rubocop/cop/rspec/be.rb +0 -1
  10. data/lib/rubocop/cop/rspec/be_eq.rb +0 -1
  11. data/lib/rubocop/cop/rspec/be_eql.rb +0 -1
  12. data/lib/rubocop/cop/rspec/before_after_all.rb +1 -0
  13. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +9 -3
  14. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +2 -1
  15. data/lib/rubocop/cop/rspec/capybara/specific_finders.rb +86 -0
  16. data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +91 -10
  17. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +0 -1
  18. data/lib/rubocop/cop/rspec/change_by_zero.rb +60 -5
  19. data/lib/rubocop/cop/rspec/class_check.rb +101 -0
  20. data/lib/rubocop/cop/rspec/context_method.rb +2 -1
  21. data/lib/rubocop/cop/rspec/context_wording.rb +49 -18
  22. data/lib/rubocop/cop/rspec/describe_class.rb +1 -1
  23. data/lib/rubocop/cop/rspec/describe_method.rb +1 -0
  24. data/lib/rubocop/cop/rspec/described_class.rb +4 -14
  25. data/lib/rubocop/cop/rspec/dialect.rb +1 -0
  26. data/lib/rubocop/cop/rspec/empty_example_group.rb +19 -4
  27. data/lib/rubocop/cop/rspec/empty_hook.rb +2 -1
  28. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +4 -9
  29. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  30. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +2 -1
  31. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +32 -2
  32. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +2 -1
  33. data/lib/rubocop/cop/rspec/example_length.rb +2 -1
  34. data/lib/rubocop/cop/rspec/example_without_description.rb +2 -1
  35. data/lib/rubocop/cop/rspec/example_wording.rb +2 -1
  36. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +1 -0
  37. data/lib/rubocop/cop/rspec/expect_actual.rb +3 -0
  38. data/lib/rubocop/cop/rspec/expect_change.rb +1 -1
  39. data/lib/rubocop/cop/rspec/expect_in_hook.rb +4 -1
  40. data/lib/rubocop/cop/rspec/expect_output.rb +1 -0
  41. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +2 -1
  42. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +26 -12
  43. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +1 -0
  44. data/lib/rubocop/cop/rspec/file_path.rb +6 -3
  45. data/lib/rubocop/cop/rspec/focus.rb +18 -0
  46. data/lib/rubocop/cop/rspec/hook_argument.rb +7 -2
  47. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +10 -9
  48. data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +0 -1
  49. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +1 -0
  50. data/lib/rubocop/cop/rspec/implicit_expect.rb +0 -2
  51. data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
  52. data/lib/rubocop/cop/rspec/instance_variable.rb +0 -1
  53. data/lib/rubocop/cop/rspec/it_behaves_like.rb +1 -0
  54. data/lib/rubocop/cop/rspec/iterated_expectation.rb +16 -0
  55. data/lib/rubocop/cop/rspec/leading_subject.rb +15 -15
  56. data/lib/rubocop/cop/rspec/let_before_examples.rb +7 -8
  57. data/lib/rubocop/cop/rspec/let_setup.rb +4 -4
  58. data/lib/rubocop/cop/rspec/message_chain.rb +1 -1
  59. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +2 -1
  60. data/lib/rubocop/cop/rspec/mixin/css_selector.rb +99 -0
  61. data/lib/rubocop/cop/rspec/mixin/namespace.rb +23 -0
  62. data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -0
  63. data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -5
  64. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +1 -3
  65. data/lib/rubocop/cop/rspec/multiple_subjects.rb +17 -2
  66. data/lib/rubocop/cop/rspec/named_subject.rb +2 -1
  67. data/lib/rubocop/cop/rspec/nested_groups.rb +45 -25
  68. data/lib/rubocop/cop/rspec/no_expectation_example.rb +64 -0
  69. data/lib/rubocop/cop/rspec/not_to_not.rb +1 -2
  70. data/lib/rubocop/cop/rspec/overwriting_setup.rb +2 -1
  71. data/lib/rubocop/cop/rspec/pending.rb +1 -0
  72. data/lib/rubocop/cop/rspec/predicate_matcher.rb +2 -1
  73. data/lib/rubocop/cop/rspec/rails/avoid_setup_hook.rb +1 -2
  74. data/lib/rubocop/cop/rspec/receive_counts.rb +14 -15
  75. data/lib/rubocop/cop/rspec/receive_never.rb +4 -5
  76. data/lib/rubocop/cop/rspec/repeated_description.rb +25 -26
  77. data/lib/rubocop/cop/rspec/repeated_example.rb +1 -1
  78. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +28 -29
  79. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +28 -29
  80. data/lib/rubocop/cop/rspec/repeated_include_example.rb +32 -33
  81. data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
  82. data/lib/rubocop/cop/rspec/scattered_let.rb +1 -5
  83. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  84. data/lib/rubocop/cop/rspec/shared_context.rb +1 -1
  85. data/lib/rubocop/cop/rspec/stubbed_mock.rb +0 -1
  86. data/lib/rubocop/cop/rspec/subject_declaration.rb +0 -1
  87. data/lib/rubocop/cop/rspec/subject_stub.rb +2 -2
  88. data/lib/rubocop/cop/rspec/unspecified_exception.rb +15 -15
  89. data/lib/rubocop/cop/rspec/variable_definition.rb +1 -0
  90. data/lib/rubocop/cop/rspec/variable_name.rb +6 -7
  91. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -0
  92. data/lib/rubocop/cop/rspec/void_expect.rb +2 -1
  93. data/lib/rubocop/cop/rspec/yield.rb +2 -1
  94. data/lib/rubocop/cop/rspec_cops.rb +3 -0
  95. data/lib/rubocop/rspec/config_formatter.rb +14 -3
  96. data/lib/rubocop/rspec/inject.rb +1 -3
  97. data/lib/rubocop/rspec/language/node_pattern.rb +4 -0
  98. data/lib/rubocop/rspec/language.rb +6 -1
  99. data/lib/rubocop/rspec/version.rb +1 -1
  100. data/lib/rubocop/rspec/wording.rb +2 -2
  101. data/lib/rubocop/rspec.rb +14 -0
  102. data/lib/rubocop-rspec.rb +3 -0
  103. metadata +11 -88
@@ -18,6 +18,7 @@ module RuboCop
18
18
  #
19
19
  # # good
20
20
  # it_should_behave_like 'a foo'
21
+ #
21
22
  class ItBehavesLike < Base
22
23
  extend AutoCorrector
23
24
  include ConfigurableEnforcedStyle
@@ -15,6 +15,7 @@ module RuboCop
15
15
  # it 'validates users' do
16
16
  # expect([user1, user2, user3]).to all(be_valid)
17
17
  # end
18
+ #
18
19
  class IteratedExpectation < Base
19
20
  MSG = 'Prefer using the `all` matcher instead ' \
20
21
  'of iterating over an array.'
@@ -28,6 +29,13 @@ module RuboCop
28
29
  )
29
30
  PATTERN
30
31
 
32
+ # @!method each_numblock?(node)
33
+ def_node_matcher :each_numblock?, <<-PATTERN
34
+ (numblock
35
+ (send ... :each) _ $(...)
36
+ )
37
+ PATTERN
38
+
31
39
  # @!method expectation?(node)
32
40
  def_node_matcher :expectation?, <<-PATTERN
33
41
  (send (send nil? :expect (lvar %)) :to ...)
@@ -41,6 +49,14 @@ module RuboCop
41
49
  end
42
50
  end
43
51
 
52
+ def on_numblock(node)
53
+ each_numblock?(node) do |body|
54
+ if single_expectation?(body, :_1) || only_expectations?(body, :_1)
55
+ add_offense(node.send_node)
56
+ end
57
+ end
58
+ end
59
+
44
60
  private
45
61
 
46
62
  def single_expectation?(body, arg)
@@ -7,29 +7,29 @@ module RuboCop
7
7
  #
8
8
  # @example
9
9
  # # bad
10
- # let(:params) { blah }
11
- # subject { described_class.new(params) }
10
+ # let(:params) { blah }
11
+ # subject { described_class.new(params) }
12
12
  #
13
- # before { do_something }
14
- # subject { described_class.new(params) }
13
+ # before { do_something }
14
+ # subject { described_class.new(params) }
15
15
  #
16
- # it { expect_something }
17
- # subject { described_class.new(params) }
18
- # it { expect_something_else }
16
+ # it { expect_something }
17
+ # subject { described_class.new(params) }
18
+ # it { expect_something_else }
19
19
  #
20
20
  #
21
21
  # # good
22
- # subject { described_class.new(params) }
23
- # let(:params) { blah }
22
+ # subject { described_class.new(params) }
23
+ # let(:params) { blah }
24
24
  #
25
25
  # # good
26
- # subject { described_class.new(params) }
27
- # before { do_something }
26
+ # subject { described_class.new(params) }
27
+ # before { do_something }
28
28
  #
29
29
  # # good
30
- # subject { described_class.new(params) }
31
- # it { expect_something }
32
- # it { expect_something_else }
30
+ # subject { described_class.new(params) }
31
+ # it { expect_something }
32
+ # it { expect_something_else }
33
33
  #
34
34
  class LeadingSubject < Base
35
35
  extend AutoCorrector
@@ -37,7 +37,7 @@ module RuboCop
37
37
 
38
38
  MSG = 'Declare `subject` above any other `%<offending>s` declarations.'
39
39
 
40
- def on_block(node)
40
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
41
41
  return unless subject?(node)
42
42
  return unless inside_example_group?(node)
43
43
 
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # Checks for `let` definitions that come after an example.
7
7
  #
8
8
  # @example
9
- # # Bad
9
+ # # bad
10
10
  # let(:foo) { bar }
11
11
  #
12
12
  # it 'checks what foo does' do
@@ -19,7 +19,7 @@ module RuboCop
19
19
  # expect(some).to be
20
20
  # end
21
21
  #
22
- # # Good
22
+ # # good
23
23
  # let(:foo) { bar }
24
24
  # let(:some) { other }
25
25
  #
@@ -43,7 +43,7 @@ module RuboCop
43
43
  }
44
44
  PATTERN
45
45
 
46
- def on_block(node)
46
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
47
47
  return unless example_group_with_body?(node)
48
48
 
49
49
  check_let_declarations(node.body) if multiline_block?(node.body)
@@ -59,12 +59,11 @@ module RuboCop
59
59
  first_example = find_first_example(node)
60
60
  return unless first_example
61
61
 
62
- node.each_child_node do |child|
63
- next if child.sibling_index < first_example.sibling_index
64
- next unless let?(child)
62
+ first_example.right_siblings.each do |sibling|
63
+ next unless let?(sibling)
65
64
 
66
- add_offense(child) do |corrector|
67
- autocorrect(corrector, child, first_example)
65
+ add_offense(sibling) do |corrector|
66
+ autocorrect(corrector, sibling, first_example)
68
67
  end
69
68
  end
70
69
  end
@@ -6,20 +6,20 @@ module RuboCop
6
6
  # Checks unreferenced `let!` calls being used for test setup.
7
7
  #
8
8
  # @example
9
- # # Bad
9
+ # # bad
10
10
  # let!(:my_widget) { create(:widget) }
11
11
  #
12
12
  # it 'counts widgets' do
13
13
  # expect(Widget.count).to eq(1)
14
14
  # end
15
15
  #
16
- # # Good
16
+ # # good
17
17
  # it 'counts widgets' do
18
18
  # create(:widget)
19
19
  # expect(Widget.count).to eq(1)
20
20
  # end
21
21
  #
22
- # # Good
22
+ # # good
23
23
  # before { create(:widget) }
24
24
  #
25
25
  # it 'counts widgets' do
@@ -49,7 +49,7 @@ module RuboCop
49
49
  # @!method method_called?(node)
50
50
  def_node_search :method_called?, '(send nil? %)'
51
51
 
52
- def on_block(node)
52
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
53
53
  return unless example_or_shared_group_or_including?(node)
54
54
 
55
55
  unused_let_bang(node) do |let|
@@ -9,7 +9,7 @@ module RuboCop
9
9
  # # bad
10
10
  # allow(foo).to receive_message_chain(:bar, :baz).and_return(42)
11
11
  #
12
- # # better
12
+ # # good
13
13
  # thing = Thing.new(baz: 42)
14
14
  # allow(foo).to receive(:bar).and_return(thing)
15
15
  #
@@ -19,10 +19,11 @@ module RuboCop
19
19
  #
20
20
  # describe "A feature example" do
21
21
  # end
22
+ #
22
23
  class MissingExampleGroupArgument < Base
23
24
  MSG = 'The first argument to `%<method>s` should not be empty.'
24
25
 
25
- def on_block(node)
26
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
26
27
  return unless example_group?(node)
27
28
  return if node.send_node.arguments?
28
29
 
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Helps parsing css selector.
7
+ module CssSelector
8
+ COMMON_OPTIONS = %w[
9
+ above below left_of right_of near count minimum maximum between text
10
+ id class style visible obscured exact exact_text normalize_ws match
11
+ wait filter_set focused
12
+ ].freeze
13
+
14
+ module_function
15
+
16
+ # @param selector [String]
17
+ # @return [Boolean]
18
+ # @example
19
+ # id?('#some-id') # => true
20
+ # id?('.some-class') # => false
21
+ def id?(selector)
22
+ selector.start_with?('#')
23
+ end
24
+
25
+ # @param selector [String]
26
+ # @return [Boolean]
27
+ # @example
28
+ # attribute?('[attribute]') # => true
29
+ # attribute?('attribute') # => false
30
+ def attribute?(selector)
31
+ selector.start_with?('[')
32
+ end
33
+
34
+ # @param selector [String]
35
+ # @return [Array<String>]
36
+ # @example
37
+ # attributes('a[foo-bar_baz]') # => {"foo-bar_baz=>true}
38
+ # attributes('button[foo][bar]') # => {"foo"=>true, "bar"=>true}
39
+ # attributes('table[foo=bar]') # => {"foo"=>"'bar'"}
40
+ def attributes(selector)
41
+ selector.scan(/\[(.*?)\]/).flatten.to_h do |attr|
42
+ key, value = attr.split('=')
43
+ [key, normalize_value(value)]
44
+ end
45
+ end
46
+
47
+ # @param selector [String]
48
+ # @return [Boolean]
49
+ # @example
50
+ # common_attributes?('a[focused]') # => true
51
+ # common_attributes?('button[focused][visible]') # => true
52
+ # common_attributes?('table[id=some-id]') # => true
53
+ # common_attributes?('h1[invalid]') # => false
54
+ def common_attributes?(selector)
55
+ attributes(selector).keys.difference(COMMON_OPTIONS).none?
56
+ end
57
+
58
+ # @param selector [String]
59
+ # @return [Array<String>]
60
+ # @example
61
+ # pseudo_classes('button:not([disabled])') # => ['not()']
62
+ # pseudo_classes('a:enabled:not([valid])') # => ['enabled', 'not()']
63
+ def pseudo_classes(selector)
64
+ # Attributes must be excluded or else the colon in the `href`s URL
65
+ # will also be picked up as pseudo classes.
66
+ # "a:not([href='http://example.com']):enabled" => "a:not():enabled"
67
+ ignored_attribute = selector.gsub(/\[.*?\]/, '')
68
+ # "a:not():enabled" => ["not()", "enabled"]
69
+ ignored_attribute.scan(/:([^:]*)/).flatten
70
+ end
71
+
72
+ # @param selector [String]
73
+ # @return [Boolean]
74
+ # @example
75
+ # multiple_selectors?('a.cls b#id') # => true
76
+ # multiple_selectors?('a.cls') # => false
77
+ def multiple_selectors?(selector)
78
+ selector.match?(/[ >,+]/)
79
+ end
80
+
81
+ # @param value [String]
82
+ # @return [Boolean, String]
83
+ # @example
84
+ # normalize_value('true') # => true
85
+ # normalize_value('false') # => false
86
+ # normalize_value(nil) # => false
87
+ # normalize_value("foo") # => "'foo'"
88
+ def normalize_value(value)
89
+ case value
90
+ when 'true' then true
91
+ when 'false' then false
92
+ when nil then true
93
+ else "'#{value}'"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Helps to find namespace of the node.
7
+ module Namespace
8
+ private
9
+
10
+ # @param node [RuboCop::AST::Node]
11
+ # @return [Array<String>]
12
+ # @example
13
+ # namespace(node) # => ['A', 'B', 'C']
14
+ def namespace(node)
15
+ node
16
+ .each_ancestor(:class, :module)
17
+ .reverse_each
18
+ .flat_map { |ancestor| ancestor.defined_module_name.split('::') }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -22,6 +22,7 @@ module RuboCop
22
22
  # describe '.do_something_else' do
23
23
  # end
24
24
  # end
25
+ #
25
26
  class MultipleDescribes < Base
26
27
  include TopLevelGroup
27
28
 
@@ -11,7 +11,6 @@ module RuboCop
11
11
  # and works with `--auto-gen-config`.
12
12
  #
13
13
  # @example
14
- #
15
14
  # # bad
16
15
  # describe UserCreator do
17
16
  # it 'builds a user' do
@@ -32,7 +31,6 @@ module RuboCop
32
31
  # end
33
32
  #
34
33
  # @example `aggregate_failures: true` (default)
35
- #
36
34
  # # good - the cop ignores when RSpec aggregates failures
37
35
  # describe UserCreator do
38
36
  # it 'builds a user', :aggregate_failures do
@@ -42,7 +40,6 @@ module RuboCop
42
40
  # end
43
41
  #
44
42
  # @example `aggregate_failures: false`
45
- #
46
43
  # # Detected as an offense
47
44
  # describe UserCreator do
48
45
  # it 'builds a user', aggregate_failures: false do
@@ -52,7 +49,6 @@ module RuboCop
52
49
  # end
53
50
  #
54
51
  # @example configuration
55
- #
56
52
  # # .rubocop.yml
57
53
  # # RSpec/MultipleExpectations:
58
54
  # # Max: 2
@@ -88,7 +84,7 @@ module RuboCop
88
84
  (block (send nil? :aggregate_failures ...) ...)
89
85
  PATTERN
90
86
 
91
- def on_block(node)
87
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
92
88
  return unless example?(node)
93
89
 
94
90
  return if example_with_aggregate_failures?(node)
@@ -56,7 +56,6 @@ module RuboCop
56
56
  # end
57
57
  #
58
58
  # @example when disabling AllowSubject configuration
59
- #
60
59
  # # rubocop.yml
61
60
  # # RSpec/MultipleMemoizedHelpers:
62
61
  # # AllowSubject: false
@@ -72,7 +71,6 @@ module RuboCop
72
71
  # end
73
72
  #
74
73
  # @example with Max configuration
75
- #
76
74
  # # rubocop.yml
77
75
  # # RSpec/MultipleMemoizedHelpers:
78
76
  # # Max: 1
@@ -89,7 +87,7 @@ module RuboCop
89
87
 
90
88
  MSG = 'Example group has too many memoized helpers [%<count>d/%<max>d]'
91
89
 
92
- def on_block(node)
90
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
93
91
  return unless spec_group?(node)
94
92
 
95
93
  count = all_helpers(node).uniq.count
@@ -6,7 +6,6 @@ module RuboCop
6
6
  # Checks if an example group defines `subject` multiple times.
7
7
  #
8
8
  # @example
9
- #
10
9
  # # bad
11
10
  # describe Foo do
12
11
  # subject(:user) { User.new }
@@ -19,6 +18,21 @@ module RuboCop
19
18
  # subject(:post) { Post.new }
20
19
  # end
21
20
  #
21
+ # # bad (does not support autocorrection)
22
+ # describe Foo do
23
+ # subject!(:user) { User.new }
24
+ # subject!(:post) { Post.new }
25
+ # end
26
+ #
27
+ # # good
28
+ # describe Foo do
29
+ # before do
30
+ # User.new
31
+ # Post.new
32
+ # end
33
+ # end
34
+ #
35
+ # This cop does not support autocorrection in some cases.
22
36
  # The autocorrect behavior for this cop depends on the type of
23
37
  # duplication:
24
38
  #
@@ -33,13 +47,14 @@ module RuboCop
33
47
  # - If subjects are defined with `subject!` then we don't autocorrect.
34
48
  # This is enough of an edge case that people can just move this to
35
49
  # a `before` hook on their own
50
+ #
36
51
  class MultipleSubjects < Base
37
52
  extend AutoCorrector
38
53
  include RangeHelp
39
54
 
40
55
  MSG = 'Do not set more than one subject per example group'
41
56
 
42
- def on_block(node)
57
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
43
58
  return unless example_group?(node)
44
59
 
45
60
  subjects = RuboCop::RSpec::ExampleGroup.new(node).subjects
@@ -41,6 +41,7 @@ module RuboCop
41
41
  #
42
42
  # it { is_expected.to be_valid }
43
43
  # end
44
+ #
44
45
  class NamedSubject < Base
45
46
  MSG = 'Name your test subject if you need to reference it explicitly.'
46
47
 
@@ -55,7 +56,7 @@ module RuboCop
55
56
  # @!method subject_usage(node)
56
57
  def_node_search :subject_usage, '$(send nil? :subject)'
57
58
 
58
- def on_block(node)
59
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
59
60
  if !example_or_hook_block?(node) || ignored_shared_example?(node)
60
61
  return
61
62
  end
@@ -36,7 +36,7 @@ module RuboCop
36
36
  # end
37
37
  # end
38
38
  #
39
- # # better
39
+ # # good
40
40
  # context 'using some feature as an admin' do
41
41
  # let(:some) { :various }
42
42
  # let(:feature) { :setup }
@@ -53,34 +53,40 @@ module RuboCop
53
53
  # it 'yada yada'
54
54
  # end
55
55
  #
56
- # @example configuration
57
- #
58
- # # .rubocop.yml
59
- # # RSpec/NestedGroups:
60
- # # Max: 2
61
- #
62
- # context 'when using some feature' do
63
- # let(:some) { :various }
64
- # let(:feature) { :setup }
65
- #
66
- # context 'when user is signed in' do
67
- # let(:user) do
68
- # UserCreate.call(user_attributes)
56
+ # @example `Max: 3` (default)
57
+ # # bad
58
+ # describe Foo do
59
+ # context 'foo' do
60
+ # context 'bar' do
61
+ # context 'baz' do # flagged by rubocop
62
+ # end
69
63
  # end
64
+ # end
65
+ # end
70
66
  #
71
- # let(:user_attributes) do
72
- # {
73
- # name: 'John',
74
- # age: 22,
75
- # role: role
76
- # }
67
+ # @example `Max: 2`
68
+ # # bad
69
+ # describe Foo do
70
+ # context 'foo' do
71
+ # context 'bar' do # flagged by rubocop
72
+ # context 'baz' do # flagged by rubocop
73
+ # end
77
74
  # end
75
+ # end
76
+ # end
78
77
  #
79
- # context 'when user is an admin' do # flagged by rubocop
80
- # let(:role) { 'admin' }
78
+ # @example `AllowedGroups: [] (default)`
79
+ # describe Foo do # <-- nested groups 1
80
+ # context 'foo' do # <-- nested groups 2
81
+ # context 'bar' do # <-- nested groups 3
82
+ # end
83
+ # end
84
+ # end
81
85
  #
82
- # it 'blah blah'
83
- # it 'yada yada'
86
+ # @example `AllowedGroups: [path]`
87
+ # describe Foo do # <-- nested groups 1
88
+ # path '/foo' do # <-- nested groups 1 (not counted)
89
+ # context 'bar' do # <-- nested groups 2
84
90
  # end
85
91
  # end
86
92
  # end
@@ -113,13 +119,23 @@ module RuboCop
113
119
  example_group = example_group?(node)
114
120
  yield node, nesting if example_group && nesting > max_nesting
115
121
 
116
- next_nesting = example_group ? nesting + 1 : nesting
122
+ next_nesting = if count_up_nesting?(node, example_group)
123
+ nesting + 1
124
+ else
125
+ nesting
126
+ end
117
127
 
118
128
  node.each_child_node(:block, :begin) do |child|
119
129
  find_nested_example_groups(child, nesting: next_nesting, &block)
120
130
  end
121
131
  end
122
132
 
133
+ def count_up_nesting?(node, example_group)
134
+ example_group &&
135
+ (node.block_type? &&
136
+ !allowed_groups.include?(node.method_name))
137
+ end
138
+
123
139
  def message(nesting)
124
140
  format(MSG, total: nesting, max: max_nesting)
125
141
  end
@@ -136,6 +152,10 @@ module RuboCop
136
152
  cop_config.fetch('Max', 3)
137
153
  end
138
154
  end
155
+
156
+ def allowed_groups
157
+ @allowed_groups ||= cop_config.fetch('AllowedGroups', [])
158
+ end
139
159
  end
140
160
  end
141
161
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks if an example contains any expectation.
7
+ #
8
+ # All RSpec's example and expectation methods are covered by default.
9
+ # If you are using your own custom methods,
10
+ # add the following configuration:
11
+ #
12
+ # RSpec:
13
+ # Language:
14
+ # Examples:
15
+ # Regular:
16
+ # - custom_it
17
+ # Expectations:
18
+ # - custom_expect
19
+ #
20
+ # @example
21
+ # # bad
22
+ # it do
23
+ # a?
24
+ # end
25
+ #
26
+ # # good
27
+ # it do
28
+ # expect(a?).to be(true)
29
+ # end
30
+ #
31
+ class NoExpectationExample < Base
32
+ MSG = 'No expectation found in this example.'
33
+
34
+ # @!method regular_or_focused_example?(node)
35
+ # @param [RuboCop::AST::Node] node
36
+ # @return [Boolean]
37
+ def_node_matcher :regular_or_focused_example?, <<~PATTERN
38
+ {
39
+ #{block_pattern('{#Examples.regular | #Examples.focused}')}
40
+ #{numblock_pattern('{#Examples.regular | #Examples.focused}')}
41
+ }
42
+ PATTERN
43
+
44
+ # @!method including_any_expectation?(node)
45
+ # @param [RuboCop::AST::Node] node
46
+ # @return [Boolean]
47
+ def_node_search(
48
+ :including_any_expectation?,
49
+ send_pattern('#Expectations.all')
50
+ )
51
+
52
+ # @param [RuboCop::AST::BlockNode] node
53
+ def on_block(node)
54
+ return unless regular_or_focused_example?(node)
55
+ return if including_any_expectation?(node)
56
+
57
+ add_offense(node)
58
+ end
59
+
60
+ alias on_numblock on_block
61
+ end
62
+ end
63
+ end
64
+ end
@@ -6,7 +6,6 @@ module RuboCop
6
6
  # Checks for consistent method usage for negating expectations.
7
7
  #
8
8
  # @example `EnforcedStyle: not_to` (default)
9
- #
10
9
  # # bad
11
10
  # it '...' do
12
11
  # expect(false).to_not be_true
@@ -18,7 +17,6 @@ module RuboCop
18
17
  # end
19
18
  #
20
19
  # @example `EnforcedStyle: to_not`
21
- #
22
20
  # # bad
23
21
  # it '...' do
24
22
  # expect(false).not_to be_true
@@ -28,6 +26,7 @@ module RuboCop
28
26
  # it '...' do
29
27
  # expect(false).to_not be_true
30
28
  # end
29
+ #
31
30
  class NotToNot < Base
32
31
  extend AutoCorrector
33
32
  include ConfigurableEnforcedStyle
@@ -21,6 +21,7 @@ module RuboCop
21
21
  # let(:foo) { bar }
22
22
  # let(:baz) { baz }
23
23
  # let!(:other) { other }
24
+ #
24
25
  class OverwritingSetup < Base
25
26
  MSG = '`%<name>s` is already defined.'
26
27
 
@@ -30,7 +31,7 @@ module RuboCop
30
31
  # @!method first_argument_name(node)
31
32
  def_node_matcher :first_argument_name, '(send _ _ ({str sym} $_))'
32
33
 
33
- def on_block(node)
34
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
34
35
  return unless example_group_with_body?(node)
35
36
 
36
37
  find_duplicates(node.body) do |duplicate, name|
@@ -31,6 +31,7 @@ module RuboCop
31
31
  # # good
32
32
  # describe MyClass do
33
33
  # end
34
+ #
34
35
  class Pending < Base
35
36
  MSG = 'Pending spec found.'
36
37