Skip to content

Commit 33e5f99

Browse files
committed
Add a new Capybara/NegationMatcherAfterVisit cop
Resolve: #7 This PR adds a new `Capybara/NegationMatcherAfterVisit` cop.
1 parent 9acafc0 commit 33e5f99

File tree

9 files changed

+148
-10
lines changed

9 files changed

+148
-10
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
- Change to default `EnforcedStyle: have_no` for `Capybara/NegationMatcher` cop. ([@ydah])
2121
- Fix a false positive for `Capybara/SpecificMatcher` when `text:` or `exact_text:` with regexp. ([@ydah])
2222

23+
- Add a new `Capybara/NegationMatcherAfterVisit` cop. ([@ydah])
24+
2325
## 2.19.0 (2023-09-20)
2426

2527
- Add new `Capybara/RSpec/PredicateMatcher` cop. ([@ydah])

config/default.yml

+6
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ Capybara/NegationMatcher:
5656
- not_to
5757
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcher
5858

59+
Capybara/NegationMatcherAfterVisit:
60+
Description: Do not allow negative matchers to be used immediately after `visit`.
61+
Enabled: pending
62+
VersionAdded: "<<next>>"
63+
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcherAfterVisit
64+
5965
Capybara/RedundantWithinFind:
6066
Description: Checks for redundant `within find(...)` calls.
6167
Enabled: pending

docs/modules/ROOT/pages/cops.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* xref:cops_capybara.adoc#capybarafindallfirst[Capybara/FindAllFirst]
99
* xref:cops_capybara.adoc#capybaramatchstyle[Capybara/MatchStyle]
1010
* xref:cops_capybara.adoc#capybaranegationmatcher[Capybara/NegationMatcher]
11+
* xref:cops_capybara.adoc#capybaranegationmatcheraftervisit[Capybara/NegationMatcherAfterVisit]
1112
* xref:cops_capybara.adoc#capybararedundantwithinfind[Capybara/RedundantWithinFind]
1213
* xref:cops_capybara.adoc#capybaraspecificactions[Capybara/SpecificActions]
1314
* xref:cops_capybara.adoc#capybaraspecificfinders[Capybara/SpecificFinders]

docs/modules/ROOT/pages/cops_capybara.adoc

+46
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,52 @@ expect(page).not_to have_css('a')
324324
325325
* https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcher
326326
327+
[#capybaranegationmatcheraftervisit]
328+
== Capybara/NegationMatcherAfterVisit
329+
330+
|===
331+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
332+
333+
| Pending
334+
| Yes
335+
| No
336+
| <<next>>
337+
| -
338+
|===
339+
340+
Do not allow negative matchers to be used immediately after `visit`.
341+
342+
[#examples-capybaranegationmatcheraftervisit]
343+
=== Examples
344+
345+
[source,ruby]
346+
----
347+
# bad
348+
visit foo_path
349+
expect(page).to have_no_link('bar')
350+
expect(page).to have_css('a')
351+
352+
# good
353+
visit foo_path
354+
expect(page).to have_css('a')
355+
expect(page).to have_no_link('bar')
356+
357+
# bad
358+
visit foo_path
359+
expect(page).not_to have_link('bar')
360+
expect(page).to have_css('a')
361+
362+
# good
363+
visit foo_path
364+
expect(page).to have_css('a')
365+
expect(page).not_to have_link('bar')
366+
----
367+
368+
[#references-capybaranegationmatcheraftervisit]
369+
=== References
370+
371+
* https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcherAfterVisit
372+
327373
[#capybararedundantwithinfind]
328374
== Capybara/RedundantWithinFind
329375

lib/rubocop/cop/capybara/mixin/capybara_help.rb

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ module Capybara
66
# Help methods for capybara.
77
# @api private
88
module CapybaraHelp
9+
CAPYBARA_MATCHERS = %w[
10+
selector css xpath text title current_path link button
11+
field checked_field unchecked_field select table
12+
sibling ancestor content
13+
].freeze
14+
POSITIVE_MATCHERS =
15+
Set.new(CAPYBARA_MATCHERS) { |element| :"have_#{element}" }.freeze
16+
NEGATIVE_MATCHERS =
17+
Set.new(CAPYBARA_MATCHERS) { |element| :"have_no_#{element}" }
18+
.freeze
919
COMMON_OPTIONS = %w[
1020
id class style
1121
].freeze

lib/rubocop/cop/capybara/negation_matcher.rb

+1-10
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,9 @@ module Capybara
2626
class NegationMatcher < ::RuboCop::Cop::Base
2727
extend AutoCorrector
2828
include ConfigurableEnforcedStyle
29+
include CapybaraHelp
2930

3031
MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
31-
CAPYBARA_MATCHERS = %w[
32-
selector css xpath text title current_path link button
33-
field checked_field unchecked_field select table
34-
sibling ancestor content
35-
].freeze
36-
POSITIVE_MATCHERS =
37-
Set.new(CAPYBARA_MATCHERS) { |element| :"have_#{element}" }.freeze
38-
NEGATIVE_MATCHERS =
39-
Set.new(CAPYBARA_MATCHERS) { |element| :"have_no_#{element}" }
40-
.freeze
4132
RESTRICT_ON_SEND = (POSITIVE_MATCHERS + NEGATIVE_MATCHERS).freeze
4233

4334
# @!method not_to?(node)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Capybara
6+
# Do not allow negative matchers to be used immediately after `visit`.
7+
#
8+
# @example
9+
# # bad
10+
# visit foo_path
11+
# expect(page).to have_no_link('bar')
12+
# expect(page).to have_css('a')
13+
#
14+
# # good
15+
# visit foo_path
16+
# expect(page).to have_css('a')
17+
# expect(page).to have_no_link('bar')
18+
#
19+
# # bad
20+
# visit foo_path
21+
# expect(page).not_to have_link('bar')
22+
# expect(page).to have_css('a')
23+
#
24+
# # good
25+
# visit foo_path
26+
# expect(page).to have_css('a')
27+
# expect(page).not_to have_link('bar')
28+
#
29+
class NegationMatcherAfterVisit < ::RuboCop::Cop::Base
30+
include CapybaraHelp
31+
32+
MSG = 'Do not use negation matcher immediately after visit.'
33+
RESTRICT_ON_SEND = %i[visit].freeze
34+
35+
# @!method negation_matcher?(node)
36+
def_node_matcher :negation_matcher?, <<~PATTERN
37+
{
38+
(send (send nil? :expect _) :to (send nil? %NEGATIVE_MATCHERS ...))
39+
(send (send nil? :expect _) :not_to (send nil? %POSITIVE_MATCHERS ...))
40+
}
41+
PATTERN
42+
43+
def on_send(node)
44+
negation_matcher?(node.right_sibling) do
45+
add_offense(node.right_sibling)
46+
end
47+
end
48+
end
49+
end
50+
end
51+
end

lib/rubocop/cop/capybara_cops.rb

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require_relative 'capybara/find_all_first'
1010
require_relative 'capybara/match_style'
1111
require_relative 'capybara/negation_matcher'
12+
require_relative 'capybara/negation_matcher_after_visit'
1213
require_relative 'capybara/redundant_within_find'
1314
require_relative 'capybara/specific_actions'
1415
require_relative 'capybara/specific_finders'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Capybara::NegationMatcherAfterVisit, :config do
4+
it 'registers an offense when using `have_no_*` after ' \
5+
'immediately `visit` method call' do
6+
expect_offense(<<~RUBY)
7+
visit foo_path
8+
expect(page).to have_no_link('bar')
9+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use negation matcher immediately after visit.
10+
RUBY
11+
end
12+
13+
it 'registers an offense when using `not_to` with `have_*` after ' \
14+
'immediately `visit` method call' do
15+
expect_offense(<<~RUBY)
16+
visit foo_path
17+
expect(page).not_to have_link('bar')
18+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use negation matcher immediately after visit.
19+
RUBY
20+
end
21+
22+
it 'does not register an offense when using positive matchers after ' \
23+
'immediately `visit` method call' do
24+
expect_no_offenses(<<~RUBY)
25+
visit foo_path
26+
expect(page).to have_css('a')
27+
expect(page).to have_no_link('bar')
28+
RUBY
29+
end
30+
end

0 commit comments

Comments
 (0)