From 7b0455d2e247c7cfba0fdbace9ebc13fbaf83fa4 Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Tue, 4 Apr 2023 00:46:12 +0900 Subject: [PATCH] Add a new `Capybara/NegationMatcherAfterVisit` cop Resolve: #7 This PR adds a new `Capybara/NegationMatcherAfterVisit` cop. --- CHANGELOG.md | 1 + config/default.yml | 6 +++ docs/modules/ROOT/pages/cops.adoc | 1 + docs/modules/ROOT/pages/cops_capybara.adoc | 46 +++++++++++++++++ .../cop/capybara/mixin/capybara_help.rb | 10 ++++ lib/rubocop/cop/capybara/negation_matcher.rb | 11 +--- .../capybara/negation_matcher_after_visit.rb | 51 +++++++++++++++++++ lib/rubocop/cop/capybara_cops.rb | 1 + .../negation_matcher_after_visit_spec.rb | 30 +++++++++++ 9 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 lib/rubocop/cop/capybara/negation_matcher_after_visit.rb create mode 100644 spec/rubocop/cop/capybara/negation_matcher_after_visit_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index a868d81..0ce14d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/config/default.yml b/config/default.yml index e23f889..0923cc2 100644 --- a/config/default.yml +++ b/config/default.yml @@ -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: "<>" + Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcherAfterVisit + Capybara/RedundantWithinFind: Description: Checks for redundant `within find(...)` calls. Enabled: pending diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 4f63547..c6b8d08 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -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] diff --git a/docs/modules/ROOT/pages/cops_capybara.adoc b/docs/modules/ROOT/pages/cops_capybara.adoc index f79955e..1fea08e 100644 --- a/docs/modules/ROOT/pages/cops_capybara.adoc +++ b/docs/modules/ROOT/pages/cops_capybara.adoc @@ -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 +| <> +| - +|=== + +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 diff --git a/lib/rubocop/cop/capybara/mixin/capybara_help.rb b/lib/rubocop/cop/capybara/mixin/capybara_help.rb index d18fab6..d4b463c 100644 --- a/lib/rubocop/cop/capybara/mixin/capybara_help.rb +++ b/lib/rubocop/cop/capybara/mixin/capybara_help.rb @@ -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 diff --git a/lib/rubocop/cop/capybara/negation_matcher.rb b/lib/rubocop/cop/capybara/negation_matcher.rb index a283449..6779bbe 100644 --- a/lib/rubocop/cop/capybara/negation_matcher.rb +++ b/lib/rubocop/cop/capybara/negation_matcher.rb @@ -26,18 +26,9 @@ module Capybara class NegationMatcher < ::RuboCop::Cop::Base extend AutoCorrector include ConfigurableEnforcedStyle + include CapybaraHelp MSG = 'Use `expect(...).%s %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) diff --git a/lib/rubocop/cop/capybara/negation_matcher_after_visit.rb b/lib/rubocop/cop/capybara/negation_matcher_after_visit.rb new file mode 100644 index 0000000..10ff786 --- /dev/null +++ b/lib/rubocop/cop/capybara/negation_matcher_after_visit.rb @@ -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 diff --git a/lib/rubocop/cop/capybara_cops.rb b/lib/rubocop/cop/capybara_cops.rb index 180a767..c8c3531 100644 --- a/lib/rubocop/cop/capybara_cops.rb +++ b/lib/rubocop/cop/capybara_cops.rb @@ -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' diff --git a/spec/rubocop/cop/capybara/negation_matcher_after_visit_spec.rb b/spec/rubocop/cop/capybara/negation_matcher_after_visit_spec.rb new file mode 100644 index 0000000..0825466 --- /dev/null +++ b/spec/rubocop/cop/capybara/negation_matcher_after_visit_spec.rb @@ -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