-
-
Notifications
You must be signed in to change notification settings - Fork 276
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: #378
- Loading branch information
Showing
8 changed files
with
274 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,106 @@ | ||
# 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: not_to (default) | ||
# # 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') | ||
# | ||
# @example EnforcedStyle: have_no | ||
# # 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') | ||
# | ||
class NegationMatcher < Base | ||
extend AutoCorrector | ||
include ConfigurableEnforcedStyle | ||
|
||
MSG = 'Use `expect(...).%<runner>s %<matcher>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 = | ||
Set.new(CAPYBARA_MATCHERS) { |element| :"have_#{element}" }.freeze | ||
NEGATIVE_MATCHERS = | ||
Set.new(CAPYBARA_MATCHERS) { |element| :"have_no_#{element}" } | ||
.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 ...)) | ||
PATTERN | ||
|
||
# @!method have_no?(node) | ||
def_node_matcher :have_no?, <<~PATTERN | ||
(send ... :to | ||
(send nil? %NEGATIVE_MATCHERS ...)) | ||
PATTERN | ||
|
||
def on_send(node) | ||
return unless offense?(node.parent) | ||
|
||
matcher = node.method_name.to_s | ||
add_offense(offense_range(node), | ||
message: message(matcher)) do |corrector| | ||
corrector.replace(node.parent.loc.selector, replaced_runner) | ||
corrector.replace(node.loc.selector, | ||
replaced_matcher(matcher)) | ||
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(matcher) | ||
format(MSG, | ||
runner: replaced_runner, | ||
matcher: replaced_matcher(matcher)) | ||
end | ||
|
||
def replaced_runner | ||
case style | ||
when :have_no | ||
'to' | ||
when :not_to | ||
'not_to' | ||
end | ||
end | ||
|
||
def replaced_matcher(matcher) | ||
case style | ||
when :have_no | ||
matcher.sub('have_', 'have_no_') | ||
when :not_to | ||
matcher.sub('have_no_', 'have_') | ||
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,97 @@ | ||
# 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(...).to have_no_#{matcher}`. | ||
expect(page).not_to have_#{matcher}('a') | ||
^^^^^^^^^^^^^{matcher} Use `expect(...).to have_no_#{matcher}`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
expect(page).to have_no_#{matcher} | ||
expect(page).to have_no_#{matcher}('a') | ||
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_#{matcher} | ||
RUBY | ||
end | ||
end | ||
|
||
it 'registers an offense when using ' \ | ||
'`expect(...).not_to have_text` with heredoc' do | ||
expect_offense(<<~RUBY) | ||
expect(page).not_to have_text(exact_text: <<~TEXT) | ||
^^^^^^^^^^^^^^^^ Use `expect(...).to have_no_text`. | ||
foo | ||
TEXT | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
expect(page).to have_no_text(exact_text: <<~TEXT) | ||
foo | ||
TEXT | ||
RUBY | ||
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(...).not_to have_#{matcher}`. | ||
expect(page).to have_no_#{matcher}('a') | ||
^^^^^^^^^^^^{matcher} Use `expect(...).not_to have_#{matcher}`. | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
expect(page).not_to have_#{matcher} | ||
expect(page).not_to have_#{matcher}('a') | ||
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_#{matcher} | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when using ' \ | ||
'`expect(...).to have_no_text` with heredoc' do | ||
expect_offense(<<~RUBY) | ||
expect(page).to have_no_text(exact_text: <<~TEXT) | ||
^^^^^^^^^^^^^^^ Use `expect(...).not_to have_text`. | ||
foo | ||
TEXT | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
expect(page).not_to have_text(exact_text: <<~TEXT) | ||
foo | ||
TEXT | ||
RUBY | ||
end | ||
end | ||
end | ||
end |