Skip to content

Commit

Permalink
Add new Capybara/RSpec/HaveSelector cop
Browse files Browse the repository at this point in the history
This PR add new `Capybara/RSpec/HaveSelector` cop.

```ruby
expect(foo).to have_selector(:css, 'bar')
expect(foo).to have_css('bar')
expect(foo).to have_selector(:xpath, 'bar')
expect(foo).to have_xpath('bar')

expect(foo).to have_selector('bar')
expect(foo).to have_css('bar')

expect(foo).to have_selector('bar')
expect(foo).to have_xpath('bar')
```
  • Loading branch information
ydah committed Sep 19, 2023
1 parent 3b4381b commit 54069a0
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Drop Ruby 2.6 support. ([@ydah])
- Add new `Capybara/RSpec/PredicateMatcher` cop. ([@ydah])
- Fix a false positive for `Capybara/SpecificFinders` when `find` with kind option. ([@ydah])
- Add new `Capybara/RSpec/HaveSelector` cop. ([@ydah])

## 2.18.0 (2023-04-21)

Expand Down
7 changes: 7 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ Capybara/RSpec:
Enabled: true
Include: *1

Capybara/RSpec/HaveSelector:
Description: Use `have_css` or `have_xpath` instead of `have_selector`.
Enabled: pending
DefaultSelector: css
VersionAdded: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/HaveSelector

Capybara/RSpec/PredicateMatcher:
Description: Prefer using predicate matcher over using predicate method directly.
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 @@ -12,6 +12,7 @@

=== Department xref:cops_capybara_rspec.adoc[Capybara/RSpec]

* xref:cops_capybara_rspec.adoc#capybararspec/haveselector[Capybara/RSpec/HaveSelector]
* xref:cops_capybara_rspec.adoc#capybararspec/predicatematcher[Capybara/RSpec/PredicateMatcher]

// END_COP_LIST
67 changes: 67 additions & 0 deletions docs/modules/ROOT/pages/cops_capybara_rspec.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,72 @@
= Capybara/RSpec

== Capybara/RSpec/HaveSelector

|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed

| Pending
| Yes
| Yes
| <<next>>
| -
|===

Use `have_css` or `have_xpath` instead of `have_selector`.

=== Examples

[source,ruby]
----
# bad
expect(foo).to have_selector(:css, 'bar')
# good
expect(foo).to have_css('bar')
# bad
expect(foo).to have_selector(:xpath, 'bar')
# good
expect(foo).to have_xpath('bar')
----

==== DefaultSelector: css (default)

[source,ruby]
----
# bad
expect(foo).to have_selector('bar')
# good
expect(foo).to have_css('bar')
----

==== DefaultSelector: xpath

[source,ruby]
----
# bad
expect(foo).to have_selector('bar')
# good
expect(foo).to have_xpath('bar')
----

=== Configurable attributes

|===
| Name | Default value | Configurable values

| DefaultSelector
| `css`
| String
|===

=== References

* https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/RSpec/HaveSelector

== Capybara/RSpec/PredicateMatcher

|===
Expand Down
87 changes: 87 additions & 0 deletions lib/rubocop/cop/capybara/rspec/have_selector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Capybara
module RSpec
# Use `have_css` or `have_xpath` instead of `have_selector`.
#
# @example
# # bad
# expect(foo).to have_selector(:css, 'bar')
#
# # good
# expect(foo).to have_css('bar')
#
# # bad
# expect(foo).to have_selector(:xpath, 'bar')
#
# # good
# expect(foo).to have_xpath('bar')
#
# @example DefaultSelector: css (default)
# # bad
# expect(foo).to have_selector('bar')
#
# # good
# expect(foo).to have_css('bar')
#
# @example DefaultSelector: xpath
# # bad
# expect(foo).to have_selector('bar')
#
# # good
# expect(foo).to have_xpath('bar')
#
class HaveSelector < ::RuboCop::Cop::Base
extend AutoCorrector
include RangeHelp

MSG = 'Use `%<good>s` instead of `have_selector`.'
RESTRICT_ON_SEND = %i[have_selector].freeze
SELECTORS = %i[css xpath].freeze

def on_send(node)
argument = node.first_argument
on_select_with_type(node, argument) if argument.sym_type?
on_select_without_type(node) if argument.str_type?
end

private

def on_select_with_type(node, type)
return unless SELECTORS.include?(type.value)

add_offense(node, message: message_typed(type)) do |corrector|
corrector.remove(deletion_range(type, node.arguments[1]))
corrector.replace(node.loc.selector, "have_#{type.value}")
end
end

def message_typed(type)
format(MSG, good: "have_#{type.value}")
end

def deletion_range(first_argument, second_argument)
range_between(first_argument.source_range.begin_pos,
second_argument.source_range.begin_pos)
end

def on_select_without_type(node)
add_offense(node, message: message_untyped) do |corrector|
corrector.replace(node.loc.selector, "have_#{default_selector}")
end
end

def message_untyped
format(MSG, good: "have_#{default_selector}")
end

def default_selector
cop_config.fetch('DefaultSelector', 'css')
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
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require_relative 'capybara/rspec/have_selector'
require_relative 'capybara/rspec/predicate_matcher'

require_relative 'capybara/current_path_expectation'
Expand Down
90 changes: 90 additions & 0 deletions spec/rubocop/cop/capybara/rspec/have_selector_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Capybara::RSpec::HaveSelector, :config do
let(:cop_config) do
{ 'DefaultSelector' => default_selector }
end
let(:default_selector) { 'css' }

it 'registers an offense when using `have_selector`' do
expect_offense(<<~RUBY)
expect(foo).to have_selector('bar')
^^^^^^^^^^^^^^^^^^^^ Use `have_css` instead of `have_selector`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to have_css('bar')
RUBY
end

it 'registers an offense when using `have_selector` with `:css`' do
expect_offense(<<~RUBY)
expect(foo).to have_selector(:css, 'bar')
^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `have_css` instead of `have_selector`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to have_css('bar')
RUBY
end

it 'registers an offense when using `have_selector` with `:xpath`' do
expect_offense(<<~RUBY)
expect(foo).to have_selector(:xpath, 'bar')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `have_xpath` instead of `have_selector`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to have_xpath('bar')
RUBY
end

it 'does not register an offense when using `have_css`' do
expect_no_offenses(<<~RUBY)
expect(foo).to have_css('bar')
RUBY
end

it 'does not register an offense when using `have_selector` with other sym' do
expect_no_offenses(<<~RUBY)
expect(foo).to have_selector(:foo, 'bar')
RUBY
end

context 'when DefaultSelector is xpath' do
let(:default_selector) { 'xpath' }

it 'registers an offense when using `have_selector`' do
expect_offense(<<~RUBY)
expect(foo).to have_selector('bar')
^^^^^^^^^^^^^^^^^^^^ Use `have_xpath` instead of `have_selector`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to have_xpath('bar')
RUBY
end

it 'registers an offense when using `have_selector` with `:xpath`' do
expect_offense(<<~RUBY)
expect(foo).to have_selector(:xpath, 'bar')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `have_xpath` instead of `have_selector`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to have_xpath('bar')
RUBY
end

it 'registers an offense when using `have_selector` with `:css`' do
expect_offense(<<~RUBY)
expect(foo).to have_selector(:css, 'bar')
^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `have_css` instead of `have_selector`.
RUBY

expect_correction(<<~RUBY)
expect(foo).to have_css('bar')
RUBY
end
end
end

0 comments on commit 54069a0

Please sign in to comment.