forked from rubocop/rubocop-rspec
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new
RSpec/Capybara/NegationMatcher
cop
Resolve: rubocop#378
- Loading branch information
Showing
8 changed files
with
234 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module RSpec | ||
module Capybara | ||
# Enforces use of `have_no_*` or `not_to` for negated expectations. | ||
# | ||
# @example EnforcedStyle: have_no (default) | ||
# # bad | ||
# expect(page).not_to have_selector | ||
# expect(page).not_to have_css('a') | ||
# | ||
# # good | ||
# expect(page).to have_no_selector | ||
# expect(page).to have_no_css('a') | ||
# | ||
# @example EnforcedStyle: not_to | ||
# # bad | ||
# expect(page).to have_no_selector | ||
# expect(page).to have_no_css('a') | ||
# | ||
# # good | ||
# expect(page).not_to have_selector | ||
# expect(page).not_to have_css('a') | ||
# | ||
class NegationMatcher < Base | ||
extend AutoCorrector | ||
include ConfigurableEnforcedStyle | ||
|
||
MSG = 'Use `%<good>s` instead of `%<bad>s`.' | ||
CAPYBARA_MATCHERS = %w[ | ||
selector css xpath text title current_path link button | ||
field checked_field unchecked_field select table | ||
sibling ancestor | ||
].freeze | ||
POSITIVE_MATCHERS = CAPYBARA_MATCHERS.map do |element| | ||
"have_#{element}".to_sym | ||
end.freeze | ||
NEGATIVE_MATCHERS = CAPYBARA_MATCHERS.map do |element| | ||
"have_no_#{element}".to_sym | ||
end.freeze | ||
RESTRICT_ON_SEND = (POSITIVE_MATCHERS + NEGATIVE_MATCHERS).freeze | ||
|
||
# @!method not_to?(node) | ||
def_node_matcher :not_to?, <<~PATTERN | ||
(send ... :not_to | ||
(send nil? {#{POSITIVE_MATCHERS.map(&:inspect).join(' ')}}) ...) | ||
PATTERN | ||
|
||
# @!method have_no?(node) | ||
def_node_matcher :have_no?, <<~PATTERN | ||
(send ... :to | ||
(send nil? {#{NEGATIVE_MATCHERS.map(&:inspect).join(' ')}}) ...) | ||
PATTERN | ||
|
||
def on_send(node) | ||
return unless offense?(node.parent) | ||
|
||
add_offense(offense_range(node), | ||
message: message(node)) do |corrector| | ||
corrector.replace(node.parent.loc.selector, replaced_target) | ||
corrector.replace(node.loc.selector, replaced_matcher(node)) | ||
end | ||
end | ||
|
||
private | ||
|
||
def offense?(node) | ||
(style == :have_no && not_to?(node)) || | ||
(style == :not_to && have_no?(node)) | ||
end | ||
|
||
def offense_range(node) | ||
node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos) | ||
end | ||
|
||
def message(node) | ||
format(MSG, | ||
good: format_message(node.parent.loc.selector.source, | ||
node.loc.selector.source), | ||
bad: format_message(replaced_target, replaced_matcher(node))) | ||
end | ||
|
||
def format_message(target, matcher) | ||
"expect(...).#{target} #{matcher}" | ||
end | ||
|
||
def replaced_target | ||
style == :have_no ? 'to' : 'not_to' | ||
end | ||
|
||
def replaced_matcher(node) | ||
if style == :have_no | ||
node.loc.selector.source.gsub('have_', 'have_no_') | ||
else | ||
node.loc.selector.source.gsub('_no', '') | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::RSpec::Capybara::NegationMatcher, :config do | ||
let(:cop_config) { { 'EnforcedStyle' => enforced_style } } | ||
|
||
context 'with EnforcedStyle `have_no`' do | ||
let(:enforced_style) { 'have_no' } | ||
|
||
%i[selector css xpath text title current_path link button | ||
field checked_field unchecked_field select table | ||
sibling ancestor].each do |matcher| | ||
it 'registers an offense when using ' \ | ||
'`expect(...).not_to have_#{matcher}`' do | ||
expect_offense(<<~RUBY, matcher: matcher) | ||
expect(page).not_to have_#{matcher} | ||
^^^^^^^^^^^^^{matcher} Use `expect(...).not_to have_#{matcher}` instead of `expect(...).to have_no_#{matcher}`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
expect(page).to have_no_#{matcher} | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using ' \ | ||
'`expect(...).to have_no_#{matcher}`' do | ||
expect_no_offenses(<<~RUBY) | ||
expect(page).to have_no_selector | ||
RUBY | ||
end | ||
end | ||
end | ||
|
||
context 'with EnforcedStyle `not_to`' do | ||
let(:enforced_style) { 'not_to' } | ||
|
||
%i[selector css xpath text title current_path link button | ||
field checked_field unchecked_field select table | ||
sibling ancestor].each do |matcher| | ||
it 'registers an offense when using ' \ | ||
'`expect(...).to have_no_#{matcher}`' do | ||
expect_offense(<<~RUBY, matcher: matcher) | ||
expect(page).to have_no_#{matcher} | ||
^^^^^^^^^^^^{matcher} Use `expect(...).to have_no_#{matcher}` instead of `expect(...).not_to have_#{matcher}`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
expect(page).not_to have_#{matcher} | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when using ' \ | ||
'`expect(...).not_to have_#{matcher}`' do | ||
expect_no_offenses(<<~RUBY) | ||
expect(page).not_to have_selector | ||
RUBY | ||
end | ||
end | ||
end | ||
end |