Skip to content

Commit

Permalink
Add a new Capybara/NegationMatcherAfterVisit cop
Browse files Browse the repository at this point in the history
Resolve: #7

This PR adds a new `Capybara/NegationMatcherAfterVisit` cop.
  • Loading branch information
ydah committed Jan 9, 2025
1 parent 9acafc0 commit 7b0455d
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Add `Capybara/AmbiguousClick` cop and make soft-deprecated `Capybara/ClickLinkOrButtonStyle` cop. If you want to use `EnforcedStyle: strict`, use `Capybara/AmbiguousClick` cop instead. ([@ydah])
- Add new `Capybara/FindAllFirst` cop. ([@ydah])
- Add a new `Capybara/NegationMatcherAfterVisit` cop. ([@ydah])
- Fix an error for `Capybara/RSpec/HaveSelector` when passing no arguments. ([@earlopain])

## 2.21.0 (2024-06-08)
Expand Down
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ Capybara/NegationMatcher:
- not_to
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcher

Capybara/NegationMatcherAfterVisit:
Description: Do not allow negative matchers to be used immediately after `visit`.
Enabled: pending
VersionAdded: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcherAfterVisit

Capybara/RedundantWithinFind:
Description: Checks for redundant `within find(...)` calls.
Enabled: pending
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* xref:cops_capybara.adoc#capybarafindallfirst[Capybara/FindAllFirst]
* xref:cops_capybara.adoc#capybaramatchstyle[Capybara/MatchStyle]
* xref:cops_capybara.adoc#capybaranegationmatcher[Capybara/NegationMatcher]
* xref:cops_capybara.adoc#capybaranegationmatcheraftervisit[Capybara/NegationMatcherAfterVisit]
* xref:cops_capybara.adoc#capybararedundantwithinfind[Capybara/RedundantWithinFind]
* xref:cops_capybara.adoc#capybaraspecificactions[Capybara/SpecificActions]
* xref:cops_capybara.adoc#capybaraspecificfinders[Capybara/SpecificFinders]
Expand Down
46 changes: 46 additions & 0 deletions docs/modules/ROOT/pages/cops_capybara.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,52 @@ expect(page).not_to have_css('a')
* https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcher
[#capybaranegationmatcheraftervisit]
== Capybara/NegationMatcherAfterVisit
|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
| Pending
| Yes
| No
| <<next>>
| -
|===
Do not allow negative matchers to be used immediately after `visit`.
[#examples-capybaranegationmatcheraftervisit]
=== Examples
[source,ruby]
----
# bad
visit foo_path
expect(page).to have_no_link('bar')
expect(page).to have_css('a')
# good
visit foo_path
expect(page).to have_css('a')
expect(page).to have_no_link('bar')
# bad
visit foo_path
expect(page).not_to have_link('bar')
expect(page).to have_css('a')
# good
visit foo_path
expect(page).to have_css('a')
expect(page).not_to have_link('bar')
----
[#references-capybaranegationmatcheraftervisit]
=== References
* https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcherAfterVisit
[#capybararedundantwithinfind]
== Capybara/RedundantWithinFind
Expand Down
10 changes: 10 additions & 0 deletions lib/rubocop/cop/capybara/mixin/capybara_help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ module Capybara
# Help methods for capybara.
# @api private
module CapybaraHelp
CAPYBARA_MATCHERS = %w[
selector css xpath text title current_path link button
field checked_field unchecked_field select table
sibling ancestor content
].freeze
POSITIVE_MATCHERS =
Set.new(CAPYBARA_MATCHERS) { |element| :"have_#{element}" }.freeze
NEGATIVE_MATCHERS =
Set.new(CAPYBARA_MATCHERS) { |element| :"have_no_#{element}" }
.freeze
COMMON_OPTIONS = %w[
id class style
].freeze
Expand Down
11 changes: 1 addition & 10 deletions lib/rubocop/cop/capybara/negation_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,9 @@ module Capybara
class NegationMatcher < ::RuboCop::Cop::Base
extend AutoCorrector
include ConfigurableEnforcedStyle
include CapybaraHelp

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 content
].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)
Expand Down
51 changes: 51 additions & 0 deletions lib/rubocop/cop/capybara/negation_matcher_after_visit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Capybara
# Do not allow negative matchers to be used immediately after `visit`.
#
# @example
# # bad
# visit foo_path
# expect(page).to have_no_link('bar')
# expect(page).to have_css('a')
#
# # good
# visit foo_path
# expect(page).to have_css('a')
# expect(page).to have_no_link('bar')
#
# # bad
# visit foo_path
# expect(page).not_to have_link('bar')
# expect(page).to have_css('a')
#
# # good
# visit foo_path
# expect(page).to have_css('a')
# expect(page).not_to have_link('bar')
#
class NegationMatcherAfterVisit < ::RuboCop::Cop::Base
include CapybaraHelp

MSG = 'Do not use negation matcher immediately after visit.'
RESTRICT_ON_SEND = %i[visit].freeze

# @!method negation_matcher?(node)
def_node_matcher :negation_matcher?, <<~PATTERN
{
(send (send nil? :expect _) :to (send nil? %NEGATIVE_MATCHERS ...))
(send (send nil? :expect _) :not_to (send nil? %POSITIVE_MATCHERS ...))
}
PATTERN

def on_send(node)
negation_matcher?(node.right_sibling) do
add_offense(node.right_sibling)
end
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/capybara_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
require_relative 'capybara/find_all_first'
require_relative 'capybara/match_style'
require_relative 'capybara/negation_matcher'
require_relative 'capybara/negation_matcher_after_visit'
require_relative 'capybara/redundant_within_find'
require_relative 'capybara/specific_actions'
require_relative 'capybara/specific_finders'
Expand Down
30 changes: 30 additions & 0 deletions spec/rubocop/cop/capybara/negation_matcher_after_visit_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Capybara::NegationMatcherAfterVisit, :config do
it 'registers an offense when using `have_no_*` after ' \
'immediately `visit` method call' do
expect_offense(<<~RUBY)
visit foo_path
expect(page).to have_no_link('bar')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use negation matcher immediately after visit.
RUBY
end

it 'registers an offense when using `not_to` with `have_*` after ' \
'immediately `visit` method call' do
expect_offense(<<~RUBY)
visit foo_path
expect(page).not_to have_link('bar')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use negation matcher immediately after visit.
RUBY
end

it 'does not register an offense when using positive matchers after ' \
'immediately `visit` method call' do
expect_no_offenses(<<~RUBY)
visit foo_path
expect(page).to have_css('a')
expect(page).to have_no_link('bar')
RUBY
end
end

0 comments on commit 7b0455d

Please sign in to comment.