Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
11 changes: 4 additions & 7 deletions lib/rubocop/cop/rspec/repeated_example_group_description.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ module RSpec
class RepeatedExampleGroupDescription < Base
include SkipOrPending

MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
MSG = 'Repeated %<group>s block description on line(s) %<loc>s.'

# @!method several_example_groups?(node)
def_node_matcher :several_example_groups?, <<~PATTERN
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down
219 changes: 198 additions & 21 deletions spec/rubocop/cop/rspec/repeated_example_group_description_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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