diff --git a/CHANGELOG.md b/CHANGELOG.md index 82dda6d74..c40319c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Fix a false positive for `RSpec/ReceiveNever` cop when `allow(...).to receive(...).never`. ([@ydah]) - Fix detection of nameless doubles with methods in `RSpec/VerifiedDoubles`. ([@ushi-as]) - Improve an offense message for `RSpec/RepeatedExample` cop. ([@ydah]) +- Fix false negative for `RSpec/RepeatedExampleGroupDescription` cop when include skip/pending. ([@ydah]) ## 3.7.0 (2025-09-01) diff --git a/lib/rubocop/cop/rspec/repeated_example_group_description.rb b/lib/rubocop/cop/rspec/repeated_example_group_description.rb index 1e534141d..f48ef60ae 100644 --- a/lib/rubocop/cop/rspec/repeated_example_group_description.rb +++ b/lib/rubocop/cop/rspec/repeated_example_group_description.rb @@ -45,7 +45,7 @@ module RSpec class RepeatedExampleGroupDescription < Base include SkipOrPending - MSG = 'Repeated %s block description on line(s) %s' + MSG = 'Repeated %s block description on line(s) %s.' # @!method several_example_groups?(node) def_node_matcher :several_example_groups?, <<~PATTERN @@ -64,7 +64,9 @@ def on_begin(node) return unless several_example_groups?(node) repeated_group_descriptions(node).each do |group, repeats| - add_offense(group, message: message(group, repeats)) + message = format(MSG, group: group.method_name, + loc: repeats.join(', ')) + add_offense(group, message: message) end end @@ -74,7 +76,6 @@ def repeated_group_descriptions(node) node .children .select { |child| example_group?(child) } - .reject { |child| skip_or_pending_inside_block?(child) } .reject { |child| empty_description?(child) } .group_by { |group| doc_string_and_metadata(group) } .values @@ -86,10 +87,6 @@ def add_repeated_lines(groups) repeated_lines = groups.map(&:first_line) groups.map { |group| [group, repeated_lines - [group.first_line]] } end - - def message(group, repeats) - format(MSG, group: group.method_name, loc: repeats) - end end end end diff --git a/spec/rubocop/cop/rspec/repeated_example_group_description_spec.rb b/spec/rubocop/cop/rspec/repeated_example_group_description_spec.rb index 96891b0f7..7ed7f6627 100644 --- a/spec/rubocop/cop/rspec/repeated_example_group_description_spec.rb +++ b/spec/rubocop/cop/rspec/repeated_example_group_description_spec.rb @@ -4,12 +4,12 @@ it 'registers an offense for repeated describe descriptions' do expect_offense(<<~RUBY) describe 'doing x' do - ^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) [5] + ^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) 5. # example group end describe 'doing x' do - ^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) [1] + ^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) 1. # example group end RUBY @@ -18,12 +18,12 @@ it 'registers an offense for repeated context descriptions' do expect_offense(<<~RUBY) context 'when awesome case' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [5] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 5. # example group end context 'when awesome case' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [1] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 1. # example group end RUBY @@ -37,17 +37,17 @@ end context 'when awesome case' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [10, 14] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 10, 14. # example group end context 'when awesome case' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [6, 14] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 6, 14. # example group end context 'when awesome case' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [6, 10] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 6, 10. # example group end end @@ -94,12 +94,12 @@ 'similar descriptions' do expect_offense(<<~RUBY) describe 'Animal' do - ^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) [5] + ^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) 5. # example group end context 'Animal' do - ^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [1] + ^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 1. # example group end RUBY @@ -112,12 +112,12 @@ end RSpec.describe 'doing x' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) [9] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) 9. it { cool_predicate_method } end context 'doing x' do - ^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [5] + ^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 5. it { cool_predicate_method } end RUBY @@ -126,12 +126,12 @@ it 'registers offense only for RSPEC namespace example groups in any order' do expect_offense(<<~RUBY) RSpec.describe 'doing x' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) [5] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) 5. it { cool_predicate_method } end context 'doing x' do - ^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [1] + ^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 1. it { cool_predicate_method } end @@ -149,12 +149,12 @@ before { create(:admin) } describe '#load' do - ^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) [10] + ^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) 10. it { cool_predicate_method } end describe '#load' do - ^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) [6] + ^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) 6. it { cool_predicate_method } end end @@ -176,12 +176,12 @@ it 'registers offense correctly for interpolated docstrings' do expect_offense(<<~RUBY) context "when class is \#{A::B}" do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [5] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 5. # ... end context "when class is \#{A::B}" do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [1] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 1. # ... end RUBY @@ -202,12 +202,12 @@ it 'registers offense if same method used in docstring' do expect_offense(<<~RUBY) context(description) do - ^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [5] + ^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 5. # ... end context(description) do - ^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) [1] + ^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 1. # ... end RUBY @@ -216,14 +216,14 @@ it 'registers offense correctly if example groups are separated' do expect_offense(<<~RUBY) describe 'repeated' do - ^^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) [7] + ^^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) 7. it { is_expected.to be_truthy } end before { do_something } describe 'repeated' do - ^^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) [1] + ^^^^^^^^^^^^^^^^^^^^^^ Repeated describe block description on line(s) 1. it { is_expected.to be_truthy } end RUBY @@ -240,4 +240,181 @@ end RUBY end + + it 'does not register offense for non-repeating group examples with skip' do + expect_no_offenses(<<~RUBY) + describe 'Something' do + context 'when foo is true' do + skip + end + + context 'when foo is false' do + skip + end + end + RUBY + end + + it 'does not register offense for non-repeating group examples ' \ + 'with pending' do + expect_no_offenses(<<~RUBY) + describe 'Something' do + context 'when foo is true' do + pending + end + + context 'when foo is false' do + pending + end + end + RUBY + end + + it 'registers offense for repeated descriptions with pending examples' do + expect_offense(<<~RUBY) + describe 'Screenshots::CreateInteractor' do + context 'when the request is valid' do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 6. + pending 'add something' + end + + context 'when the request is valid' do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 2. + pending 'add something' + end + end + RUBY + end + + it 'registers offense for repeated descriptions with skip examples' do + expect_offense(<<~RUBY) + describe 'Something' do + context 'when foo' do + ^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 6. + skip 'not implemented' + end + + context 'when foo' do + ^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 2. + skip 'not implemented' + end + end + RUBY + end + + it 'registers offense for repeated descriptions with pending metadata' do + expect_offense(<<~RUBY) + describe 'Something' do + context 'when foo', :pending do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 5. + end + + context 'when foo', :pending do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 2. + end + end + RUBY + end + + it 'registers offense for repeated descriptions with skip metadata' do + expect_offense(<<~RUBY) + describe 'Something' do + context 'when foo', :skip do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 5. + end + + context 'when foo', :skip do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 2. + end + end + RUBY + end + + it 'registers offense for repeated descriptions with skip metadata hash' do + expect_offense(<<~RUBY) + describe 'Something' do + context 'when foo', skip: true do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 5. + end + + context 'when foo', skip: true do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 2. + end + end + RUBY + end + + it 'registers offense for repeated descriptions with pending metadata hash' do + expect_offense(<<~RUBY) + describe 'Something' do + context 'when foo', pending: 'not ready' do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 5. + end + + context 'when foo', pending: 'not ready' do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 2. + end + end + RUBY + end + + it 'registers offense with mixed pending and skip' do + expect_offense(<<~RUBY) + describe 'Something' do + context 'when foo' do + ^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 6. + pending 'add something' + end + + context 'when foo' do + ^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 2. + skip 'not ready' + end + end + RUBY + end + + it 'registers offense for repeated descriptions with xcontext' do + expect_offense(<<~RUBY) + describe 'Something' do + xcontext 'when foo' do + ^^^^^^^^^^^^^^^^^^^^^^ Repeated xcontext block description on line(s) 5. + end + + xcontext 'when foo' do + ^^^^^^^^^^^^^^^^^^^^^^ Repeated xcontext block description on line(s) 2. + end + end + RUBY + end + + it 'registers offense for repeated descriptions with xdescribe' do + expect_offense(<<~RUBY) + xdescribe 'Something' do + ^^^^^^^^^^^^^^^^^^^^^^^^ Repeated xdescribe block description on line(s) 5. + it { is_expected.to be_valid } + end + + xdescribe 'Something' do + ^^^^^^^^^^^^^^^^^^^^^^^^ Repeated xdescribe block description on line(s) 1. + it { is_expected.to be_valid } + end + RUBY + end + + it 'registers offense when one group has pending and other does not' do + expect_offense(<<~RUBY) + describe 'Something' do + context 'when foo' do + ^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 6. + pending 'add something' + end + + context 'when foo' do + ^^^^^^^^^^^^^^^^^^^^^ Repeated context block description on line(s) 2. + it { is_expected.to be_valid } + end + end + RUBY + end end