brew vendor-gems: commit updates.
This commit is contained in:
parent
b63eb43b84
commit
76822805f2
@ -105,9 +105,10 @@ $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version
|
|||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-progressbar-1.11.0/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-progressbar-1.11.0/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/unicode-display_width-2.4.2/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/unicode-display_width-2.4.2/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-1.43.0/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-1.43.0/lib")
|
||||||
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-capybara-2.17.0/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-performance-1.15.2/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-performance-1.15.2/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-rails-2.17.4/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-rails-2.17.4/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-rspec-2.17.0/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-rspec-2.18.0/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-sorbet-0.6.11/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rubocop-sorbet-0.6.11/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-macho-3.0.0/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ruby-macho-3.0.0/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov-html-0.12.3/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simplecov-html-0.12.3/lib")
|
||||||
|
|||||||
24
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-capybara-2.17.0/lib/rubocop-capybara.rb
vendored
Normal file
24
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-capybara-2.17.0/lib/rubocop-capybara.rb
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'pathname'
|
||||||
|
require 'yaml'
|
||||||
|
|
||||||
|
require 'rubocop'
|
||||||
|
|
||||||
|
require_relative 'rubocop/cop/capybara/mixin/capybara_help'
|
||||||
|
require_relative 'rubocop/cop/capybara/mixin/css_selector'
|
||||||
|
|
||||||
|
require_relative 'rubocop/cop/capybara_cops'
|
||||||
|
|
||||||
|
project_root = File.join(__dir__, '..')
|
||||||
|
RuboCop::ConfigLoader.inject_defaults!(project_root)
|
||||||
|
obsoletion = File.join(project_root, 'config', 'obsoletion.yml')
|
||||||
|
RuboCop::ConfigObsoletion.files << obsoletion if File.exist?(obsoletion)
|
||||||
|
|
||||||
|
RuboCop::Cop::Style::TrailingCommaInArguments.singleton_class.prepend(
|
||||||
|
Module.new do
|
||||||
|
def autocorrect_incompatible_with
|
||||||
|
super.push(RuboCop::Cop::Capybara::CurrentPathExpectation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
@ -3,13 +3,13 @@
|
|||||||
require 'yaml'
|
require 'yaml'
|
||||||
|
|
||||||
module RuboCop
|
module RuboCop
|
||||||
module RSpec
|
module Capybara
|
||||||
# Builds a YAML config file from two config hashes
|
# Builds a YAML config file from two config hashes
|
||||||
class ConfigFormatter
|
class ConfigFormatter
|
||||||
EXTENSION_ROOT_DEPARTMENT = %r{^(RSpec/)}.freeze
|
EXTENSION_ROOT_DEPARTMENT = %r{^(Capybara/)}.freeze
|
||||||
SUBDEPARTMENTS = %(RSpec/Capybara RSpec/FactoryBot RSpec/Rails)
|
SUBDEPARTMENTS = [].freeze
|
||||||
AMENDMENTS = %(Metrics/BlockLength)
|
AMENDMENTS = [].freeze
|
||||||
COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/'
|
COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/'
|
||||||
|
|
||||||
def initialize(config, descriptions)
|
def initialize(config, descriptions)
|
||||||
@config = config
|
@config = config
|
||||||
@ -19,9 +19,9 @@ module RuboCop
|
|||||||
def dump
|
def dump
|
||||||
YAML.dump(unified_config)
|
YAML.dump(unified_config)
|
||||||
.gsub(EXTENSION_ROOT_DEPARTMENT, "\n\\1")
|
.gsub(EXTENSION_ROOT_DEPARTMENT, "\n\\1")
|
||||||
.gsub(*AMENDMENTS, "\n\\0")
|
|
||||||
.gsub(/^(\s+)- /, '\1 - ')
|
.gsub(/^(\s+)- /, '\1 - ')
|
||||||
.gsub('"~"', '~')
|
.gsub('"~"', '~')
|
||||||
|
# .gsub(*AMENDMENTS, "\n\\0")
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Capybara
|
||||||
|
# Extracts cop descriptions from YARD docstrings
|
||||||
|
class DescriptionExtractor
|
||||||
|
def initialize(yardocs)
|
||||||
|
@code_objects = yardocs.map(&CodeObject.public_method(:new))
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_h
|
||||||
|
code_objects
|
||||||
|
.select(&:cop?)
|
||||||
|
.map(&:configuration)
|
||||||
|
.reduce(:merge)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :code_objects
|
||||||
|
|
||||||
|
# Decorator of a YARD code object for working with documented cops
|
||||||
|
class CodeObject
|
||||||
|
RUBOCOP_COP_CLASS_NAME = 'RuboCop::Cop::Base'
|
||||||
|
|
||||||
|
def initialize(yardoc)
|
||||||
|
@yardoc = yardoc
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test if the YARD code object documents a concrete cop class
|
||||||
|
#
|
||||||
|
# @return [Boolean]
|
||||||
|
def cop?
|
||||||
|
cop_subclass? && !abstract?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Configuration for the documented cop that would live in default.yml
|
||||||
|
#
|
||||||
|
# @return [Hash]
|
||||||
|
def configuration
|
||||||
|
{ cop_name => { 'Description' => description } }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def cop_name
|
||||||
|
Object.const_get(documented_constant).cop_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def description
|
||||||
|
yardoc.docstring.split("\n\n").first.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def documented_constant
|
||||||
|
yardoc.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def cop_subclass?
|
||||||
|
yardoc.superclass.path == RUBOCOP_COP_CLASS_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def abstract?
|
||||||
|
yardoc.tags.any? { |tag| tag.tag_name.eql?('abstract') }
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :yardoc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Capybara
|
||||||
|
# Version information for the Capybara RuboCop plugin.
|
||||||
|
module Version
|
||||||
|
STRING = '2.17.0'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module Capybara
|
||||||
|
# Checks that no expectations are set on Capybara's `current_path`.
|
||||||
|
#
|
||||||
|
# The
|
||||||
|
# https://www.rubydoc.info/github/teamcapybara/capybara/main/Capybara/RSpecMatchers#have_current_path-instance_method[`have_current_path` matcher]
|
||||||
|
# should be used on `page` to set expectations on Capybara's
|
||||||
|
# current path, since it uses
|
||||||
|
# https://github.com/teamcapybara/capybara/blob/main/README.md#asynchronous-javascript-ajax-and-friends[Capybara's waiting functionality]
|
||||||
|
# which ensures that preceding actions (like `click_link`) have
|
||||||
|
# completed.
|
||||||
|
#
|
||||||
|
# This cop does not support autocorrection in some cases.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# # bad
|
||||||
|
# expect(current_path).to eq('/callback')
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# expect(page).to have_current_path('/callback')
|
||||||
|
#
|
||||||
|
# # bad (does not support autocorrection)
|
||||||
|
# expect(page.current_path).to match(variable)
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# expect(page).to have_current_path('/callback')
|
||||||
|
#
|
||||||
|
class CurrentPathExpectation < ::RuboCop::Cop::Base
|
||||||
|
extend AutoCorrector
|
||||||
|
|
||||||
|
MSG = 'Do not set an RSpec expectation on `current_path` in ' \
|
||||||
|
'Capybara feature specs - instead, use the ' \
|
||||||
|
'`have_current_path` matcher on `page`'
|
||||||
|
|
||||||
|
RESTRICT_ON_SEND = %i[expect].freeze
|
||||||
|
|
||||||
|
# @!method expectation_set_on_current_path(node)
|
||||||
|
def_node_matcher :expectation_set_on_current_path, <<-PATTERN
|
||||||
|
(send nil? :expect (send {(send nil? :page) nil?} :current_path))
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
# Supported matchers: eq(...) / match(/regexp/) / match('regexp')
|
||||||
|
# @!method as_is_matcher(node)
|
||||||
|
def_node_matcher :as_is_matcher, <<-PATTERN
|
||||||
|
(send
|
||||||
|
#expectation_set_on_current_path ${:to :to_not :not_to}
|
||||||
|
${(send nil? :eq ...) (send nil? :match (regexp ...))})
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
# @!method regexp_str_matcher(node)
|
||||||
|
def_node_matcher :regexp_str_matcher, <<-PATTERN
|
||||||
|
(send
|
||||||
|
#expectation_set_on_current_path ${:to :to_not :not_to}
|
||||||
|
$(send nil? :match (str $_)))
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def self.autocorrect_incompatible_with
|
||||||
|
[Style::TrailingCommaInArguments]
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_send(node)
|
||||||
|
expectation_set_on_current_path(node) do
|
||||||
|
add_offense(node.loc.selector) do |corrector|
|
||||||
|
next unless node.chained?
|
||||||
|
|
||||||
|
autocorrect(corrector, node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def autocorrect(corrector, node)
|
||||||
|
as_is_matcher(node.parent) do |to_sym, matcher_node|
|
||||||
|
rewrite_expectation(corrector, node, to_sym, matcher_node)
|
||||||
|
end
|
||||||
|
|
||||||
|
regexp_str_matcher(node.parent) do |to_sym, matcher_node, regexp|
|
||||||
|
rewrite_expectation(corrector, node, to_sym, matcher_node)
|
||||||
|
convert_regexp_str_to_literal(corrector, matcher_node, regexp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rewrite_expectation(corrector, node, to_symbol, matcher_node)
|
||||||
|
current_path_node = node.first_argument
|
||||||
|
corrector.replace(current_path_node, 'page')
|
||||||
|
corrector.replace(node.parent.loc.selector, 'to')
|
||||||
|
matcher_method = if to_symbol == :to
|
||||||
|
'have_current_path'
|
||||||
|
else
|
||||||
|
'have_no_current_path'
|
||||||
|
end
|
||||||
|
corrector.replace(matcher_node.loc.selector, matcher_method)
|
||||||
|
add_ignore_query_options(corrector, node)
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_regexp_str_to_literal(corrector, matcher_node, regexp_str)
|
||||||
|
str_node = matcher_node.first_argument
|
||||||
|
regexp_expr = Regexp.new(regexp_str).inspect
|
||||||
|
corrector.replace(str_node, regexp_expr)
|
||||||
|
end
|
||||||
|
|
||||||
|
# `have_current_path` with no options will include the querystring
|
||||||
|
# while `page.current_path` does not.
|
||||||
|
# This ensures the option `ignore_query: true` is added
|
||||||
|
# except when the expectation is a regexp or string
|
||||||
|
def add_ignore_query_options(corrector, node)
|
||||||
|
expectation_node = node.parent.last_argument
|
||||||
|
expectation_last_child = expectation_node.children.last
|
||||||
|
return if %i[regexp str].include?(expectation_last_child.type)
|
||||||
|
|
||||||
|
corrector.insert_after(
|
||||||
|
expectation_last_child,
|
||||||
|
', ignore_query: true'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module Capybara
|
||||||
|
# Checks for usage of deprecated style methods.
|
||||||
|
#
|
||||||
|
# @example when using `assert_style`
|
||||||
|
# # bad
|
||||||
|
# page.find(:css, '#first').assert_style(display: 'block')
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# page.find(:css, '#first').assert_matches_style(display: 'block')
|
||||||
|
#
|
||||||
|
# @example when using `has_style?`
|
||||||
|
# # bad
|
||||||
|
# expect(page.find(:css, 'first')
|
||||||
|
# .has_style?(display: 'block')).to be true
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# expect(page.find(:css, 'first')
|
||||||
|
# .matches_style?(display: 'block')).to be true
|
||||||
|
#
|
||||||
|
# @example when using `have_style`
|
||||||
|
# # bad
|
||||||
|
# expect(page).to have_style(display: 'block')
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# expect(page).to match_style(display: 'block')
|
||||||
|
#
|
||||||
|
class MatchStyle < ::RuboCop::Cop::Base
|
||||||
|
extend AutoCorrector
|
||||||
|
|
||||||
|
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
||||||
|
RESTRICT_ON_SEND = %i[assert_style has_style? have_style].freeze
|
||||||
|
PREFERRED_METHOD = {
|
||||||
|
'assert_style' => 'assert_matches_style',
|
||||||
|
'has_style?' => 'matches_style?',
|
||||||
|
'have_style' => 'match_style'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def on_send(node)
|
||||||
|
method_node = node.loc.selector
|
||||||
|
add_offense(method_node) do |corrector|
|
||||||
|
corrector.replace(method_node,
|
||||||
|
PREFERRED_METHOD[method_node.source])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def message(node)
|
||||||
|
format(MSG, good: PREFERRED_METHOD[node.source], bad: node.source)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
# Help methods for capybara.
|
||||||
|
module CapybaraHelp
|
||||||
|
module_function
|
||||||
|
|
||||||
|
# @param node [RuboCop::AST::SendNode]
|
||||||
|
# @param locator [String]
|
||||||
|
# @param element [String]
|
||||||
|
# @return [Boolean]
|
||||||
|
def specific_option?(node, locator, element)
|
||||||
|
attrs = CssSelector.attributes(locator).keys
|
||||||
|
return false unless replaceable_element?(node, element, attrs)
|
||||||
|
|
||||||
|
attrs.all? do |attr|
|
||||||
|
CssSelector.specific_options?(element, attr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param locator [String]
|
||||||
|
# @return [Boolean]
|
||||||
|
def specific_pseudo_classes?(locator)
|
||||||
|
CssSelector.pseudo_classes(locator).all? do |pseudo_class|
|
||||||
|
replaceable_pseudo_class?(pseudo_class, locator)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param pseudo_class [String]
|
||||||
|
# @param locator [String]
|
||||||
|
# @return [Boolean]
|
||||||
|
def replaceable_pseudo_class?(pseudo_class, locator)
|
||||||
|
return false unless CssSelector.specific_pesudo_classes?(pseudo_class)
|
||||||
|
|
||||||
|
case pseudo_class
|
||||||
|
when 'not()' then replaceable_pseudo_class_not?(locator)
|
||||||
|
else true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param locator [String]
|
||||||
|
# @return [Boolean]
|
||||||
|
def replaceable_pseudo_class_not?(locator)
|
||||||
|
locator.scan(/not\(.*?\)/).all? do |negation|
|
||||||
|
CssSelector.attributes(negation).values.all? do |v|
|
||||||
|
v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param node [RuboCop::AST::SendNode]
|
||||||
|
# @param element [String]
|
||||||
|
# @param attrs [Array<String>]
|
||||||
|
# @return [Boolean]
|
||||||
|
def replaceable_element?(node, element, attrs)
|
||||||
|
case element
|
||||||
|
when 'link' then replaceable_to_link?(node, attrs)
|
||||||
|
else true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param node [RuboCop::AST::SendNode]
|
||||||
|
# @param attrs [Array<String>]
|
||||||
|
# @return [Boolean]
|
||||||
|
def replaceable_to_link?(node, attrs)
|
||||||
|
include_option?(node, :href) || attrs.include?('href')
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param node [RuboCop::AST::SendNode]
|
||||||
|
# @param option [Symbol]
|
||||||
|
# @return [Boolean]
|
||||||
|
def include_option?(node, option)
|
||||||
|
node.each_descendant(:sym).find { |opt| opt.value == option }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,144 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
# Helps parsing css selector.
|
||||||
|
module CssSelector
|
||||||
|
COMMON_OPTIONS = %w[
|
||||||
|
above below left_of right_of near count minimum maximum between text
|
||||||
|
id class style visible obscured exact exact_text normalize_ws match
|
||||||
|
wait filter_set focused
|
||||||
|
].freeze
|
||||||
|
SPECIFIC_OPTIONS = {
|
||||||
|
'button' => (
|
||||||
|
COMMON_OPTIONS + %w[disabled name value title type]
|
||||||
|
).freeze,
|
||||||
|
'link' => (
|
||||||
|
COMMON_OPTIONS + %w[href alt title download]
|
||||||
|
).freeze,
|
||||||
|
'table' => (
|
||||||
|
COMMON_OPTIONS + %w[
|
||||||
|
caption with_cols cols with_rows rows
|
||||||
|
]
|
||||||
|
).freeze,
|
||||||
|
'select' => (
|
||||||
|
COMMON_OPTIONS + %w[
|
||||||
|
disabled name placeholder options enabled_options
|
||||||
|
disabled_options selected with_selected multiple with_options
|
||||||
|
]
|
||||||
|
).freeze,
|
||||||
|
'field' => (
|
||||||
|
COMMON_OPTIONS + %w[
|
||||||
|
checked unchecked disabled valid name placeholder
|
||||||
|
validation_message readonly with type multiple
|
||||||
|
]
|
||||||
|
).freeze
|
||||||
|
}.freeze
|
||||||
|
SPECIFIC_PSEUDO_CLASSES = %w[
|
||||||
|
not() disabled enabled checked unchecked
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
# @param element [String]
|
||||||
|
# @param attribute [String]
|
||||||
|
# @return [Boolean]
|
||||||
|
# @example
|
||||||
|
# specific_pesudo_classes?('button', 'name') # => true
|
||||||
|
# specific_pesudo_classes?('link', 'invalid') # => false
|
||||||
|
def specific_options?(element, attribute)
|
||||||
|
SPECIFIC_OPTIONS.fetch(element, []).include?(attribute)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param pseudo_class [String]
|
||||||
|
# @return [Boolean]
|
||||||
|
# @example
|
||||||
|
# specific_pesudo_classes?('disabled') # => true
|
||||||
|
# specific_pesudo_classes?('first-of-type') # => false
|
||||||
|
def specific_pesudo_classes?(pseudo_class)
|
||||||
|
SPECIFIC_PSEUDO_CLASSES.include?(pseudo_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param selector [String]
|
||||||
|
# @return [Boolean]
|
||||||
|
# @example
|
||||||
|
# id?('#some-id') # => true
|
||||||
|
# id?('.some-class') # => false
|
||||||
|
def id?(selector)
|
||||||
|
selector.start_with?('#')
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param selector [String]
|
||||||
|
# @return [Boolean]
|
||||||
|
# @example
|
||||||
|
# attribute?('[attribute]') # => true
|
||||||
|
# attribute?('attribute') # => false
|
||||||
|
def attribute?(selector)
|
||||||
|
selector.start_with?('[')
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param selector [String]
|
||||||
|
# @return [Array<String>]
|
||||||
|
# @example
|
||||||
|
# attributes('a[foo-bar_baz]') # => {"foo-bar_baz=>true}
|
||||||
|
# attributes('button[foo][bar]') # => {"foo"=>true, "bar"=>true}
|
||||||
|
# attributes('table[foo=bar]') # => {"foo"=>"'bar'"}
|
||||||
|
def attributes(selector)
|
||||||
|
selector.scan(/\[(.*?)\]/).flatten.to_h do |attr|
|
||||||
|
key, value = attr.split('=')
|
||||||
|
[key, normalize_value(value)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param selector [String]
|
||||||
|
# @return [Boolean]
|
||||||
|
# @example
|
||||||
|
# common_attributes?('a[focused]') # => true
|
||||||
|
# common_attributes?('button[focused][visible]') # => true
|
||||||
|
# common_attributes?('table[id=some-id]') # => true
|
||||||
|
# common_attributes?('h1[invalid]') # => false
|
||||||
|
def common_attributes?(selector)
|
||||||
|
attributes(selector).keys.difference(COMMON_OPTIONS).none?
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param selector [String]
|
||||||
|
# @return [Array<String>]
|
||||||
|
# @example
|
||||||
|
# pseudo_classes('button:not([disabled])') # => ['not()']
|
||||||
|
# pseudo_classes('a:enabled:not([valid])') # => ['enabled', 'not()']
|
||||||
|
def pseudo_classes(selector)
|
||||||
|
# Attributes must be excluded or else the colon in the `href`s URL
|
||||||
|
# will also be picked up as pseudo classes.
|
||||||
|
# "a:not([href='http://example.com']):enabled" => "a:not():enabled"
|
||||||
|
ignored_attribute = selector.gsub(/\[.*?\]/, '')
|
||||||
|
# "a:not():enabled" => ["not()", "enabled"]
|
||||||
|
ignored_attribute.scan(/:([^:]*)/).flatten
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param selector [String]
|
||||||
|
# @return [Boolean]
|
||||||
|
# @example
|
||||||
|
# multiple_selectors?('a.cls b#id') # => true
|
||||||
|
# multiple_selectors?('a.cls') # => false
|
||||||
|
def multiple_selectors?(selector)
|
||||||
|
selector.match?(/[ >,+~]/)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param value [String]
|
||||||
|
# @return [Boolean, String]
|
||||||
|
# @example
|
||||||
|
# normalize_value('true') # => true
|
||||||
|
# normalize_value('false') # => false
|
||||||
|
# normalize_value(nil) # => false
|
||||||
|
# normalize_value("foo") # => "'foo'"
|
||||||
|
def normalize_value(value)
|
||||||
|
case value
|
||||||
|
when 'true' then true
|
||||||
|
when 'false' then false
|
||||||
|
when nil then true
|
||||||
|
else "'#{value}'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
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 < ::RuboCop::Cop::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
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module Capybara
|
||||||
|
# Checks for there is a more specific actions offered by Capybara.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
#
|
||||||
|
# # bad
|
||||||
|
# find('a').click
|
||||||
|
# find('button.cls').click
|
||||||
|
# find('a', exact_text: 'foo').click
|
||||||
|
# find('div button').click
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# click_link
|
||||||
|
# click_button(class: 'cls')
|
||||||
|
# click_link(exact_text: 'foo')
|
||||||
|
# find('div').click_button
|
||||||
|
#
|
||||||
|
class SpecificActions < ::RuboCop::Cop::Base
|
||||||
|
MSG = "Prefer `%<good_action>s` over `find('%<selector>s').click`."
|
||||||
|
RESTRICT_ON_SEND = %i[click].freeze
|
||||||
|
SPECIFIC_ACTION = {
|
||||||
|
'button' => 'button',
|
||||||
|
'a' => 'link'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# @!method click_on_selector(node)
|
||||||
|
def_node_matcher :click_on_selector, <<-PATTERN
|
||||||
|
(send _ :find (str $_) ...)
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def on_send(node)
|
||||||
|
click_on_selector(node.receiver) do |arg|
|
||||||
|
next unless supported_selector?(arg)
|
||||||
|
# Always check the last selector in the case of multiple selectors
|
||||||
|
# separated by whitespace.
|
||||||
|
# because the `.click` is executed on the element to
|
||||||
|
# which the last selector points.
|
||||||
|
next unless (selector = last_selector(arg))
|
||||||
|
next unless (action = specific_action(selector))
|
||||||
|
next unless CapybaraHelp.specific_option?(node.receiver, arg,
|
||||||
|
action)
|
||||||
|
next unless CapybaraHelp.specific_pseudo_classes?(arg)
|
||||||
|
|
||||||
|
range = offense_range(node, node.receiver)
|
||||||
|
add_offense(range, message: message(action, selector))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def specific_action(selector)
|
||||||
|
SPECIFIC_ACTION[last_selector(selector)]
|
||||||
|
end
|
||||||
|
|
||||||
|
def supported_selector?(selector)
|
||||||
|
!selector.match?(/[>,+~]/)
|
||||||
|
end
|
||||||
|
|
||||||
|
def last_selector(arg)
|
||||||
|
arg.split.last[/^\w+/, 0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def offense_range(node, receiver)
|
||||||
|
receiver.loc.selector.with(end_pos: node.loc.expression.end_pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
def message(action, selector)
|
||||||
|
format(MSG,
|
||||||
|
good_action: good_action(action),
|
||||||
|
selector: selector)
|
||||||
|
end
|
||||||
|
|
||||||
|
def good_action(action)
|
||||||
|
"click_#{action}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module Capybara
|
||||||
|
# Checks if there is a more specific finder offered by Capybara.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# # bad
|
||||||
|
# find('#some-id')
|
||||||
|
# find('[visible][id=some-id]')
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# find_by_id('some-id')
|
||||||
|
# find_by_id('some-id', visible: true)
|
||||||
|
#
|
||||||
|
class SpecificFinders < ::RuboCop::Cop::Base
|
||||||
|
extend AutoCorrector
|
||||||
|
|
||||||
|
include RangeHelp
|
||||||
|
|
||||||
|
MSG = 'Prefer `find_by` over `find`.'
|
||||||
|
RESTRICT_ON_SEND = %i[find].freeze
|
||||||
|
|
||||||
|
# @!method find_argument(node)
|
||||||
|
def_node_matcher :find_argument, <<~PATTERN
|
||||||
|
(send _ :find (str $_) ...)
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def on_send(node)
|
||||||
|
find_argument(node) do |arg|
|
||||||
|
next if CssSelector.multiple_selectors?(arg)
|
||||||
|
|
||||||
|
on_attr(node, arg) if attribute?(arg)
|
||||||
|
on_id(node, arg) if CssSelector.id?(arg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def on_attr(node, arg)
|
||||||
|
return unless (id = CssSelector.attributes(arg)['id'])
|
||||||
|
|
||||||
|
register_offense(node, replaced_arguments(arg, id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_id(node, arg)
|
||||||
|
register_offense(node, "'#{arg.to_s.delete('#')}'")
|
||||||
|
end
|
||||||
|
|
||||||
|
def attribute?(arg)
|
||||||
|
CssSelector.attribute?(arg) &&
|
||||||
|
CssSelector.common_attributes?(arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
def register_offense(node, arg_replacement)
|
||||||
|
add_offense(offense_range(node)) do |corrector|
|
||||||
|
corrector.replace(node.loc.selector, 'find_by_id')
|
||||||
|
corrector.replace(node.first_argument.loc.expression,
|
||||||
|
arg_replacement)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def replaced_arguments(arg, id)
|
||||||
|
options = to_options(CssSelector.attributes(arg))
|
||||||
|
options.empty? ? id : "#{id}, #{options}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_options(attrs)
|
||||||
|
attrs.each.map do |key, value|
|
||||||
|
next if key == 'id'
|
||||||
|
|
||||||
|
"#{key}: #{value}"
|
||||||
|
end.compact.join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
|
def offense_range(node)
|
||||||
|
range_between(node.loc.selector.begin_pos, end_pos(node))
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_pos(node)
|
||||||
|
if node.loc.end
|
||||||
|
node.loc.end.end_pos
|
||||||
|
else
|
||||||
|
node.loc.expression.end_pos
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module Capybara
|
||||||
|
# Checks for there is a more specific matcher offered by Capybara.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
#
|
||||||
|
# # bad
|
||||||
|
# expect(page).to have_selector('button')
|
||||||
|
# expect(page).to have_no_selector('button.cls')
|
||||||
|
# expect(page).to have_css('button')
|
||||||
|
# expect(page).to have_no_css('a.cls', href: 'http://example.com')
|
||||||
|
# expect(page).to have_css('table.cls')
|
||||||
|
# expect(page).to have_css('select')
|
||||||
|
# expect(page).to have_css('input', exact_text: 'foo')
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# expect(page).to have_button
|
||||||
|
# expect(page).to have_no_button(class: 'cls')
|
||||||
|
# expect(page).to have_button
|
||||||
|
# expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
|
||||||
|
# expect(page).to have_table(class: 'cls')
|
||||||
|
# expect(page).to have_select
|
||||||
|
# expect(page).to have_field('foo')
|
||||||
|
#
|
||||||
|
class SpecificMatcher < ::RuboCop::Cop::Base
|
||||||
|
MSG = 'Prefer `%<good_matcher>s` over `%<bad_matcher>s`.'
|
||||||
|
RESTRICT_ON_SEND = %i[have_selector have_no_selector have_css
|
||||||
|
have_no_css].freeze
|
||||||
|
SPECIFIC_MATCHER = {
|
||||||
|
'button' => 'button',
|
||||||
|
'a' => 'link',
|
||||||
|
'table' => 'table',
|
||||||
|
'select' => 'select',
|
||||||
|
'input' => 'field'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# @!method first_argument(node)
|
||||||
|
def_node_matcher :first_argument, <<-PATTERN
|
||||||
|
(send nil? _ (str $_) ... )
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def on_send(node)
|
||||||
|
first_argument(node) do |arg|
|
||||||
|
next unless (matcher = specific_matcher(arg))
|
||||||
|
next if CssSelector.multiple_selectors?(arg)
|
||||||
|
next unless CapybaraHelp.specific_option?(node, arg, matcher)
|
||||||
|
next unless CapybaraHelp.specific_pseudo_classes?(arg)
|
||||||
|
|
||||||
|
add_offense(node, message: message(node, matcher))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def specific_matcher(arg)
|
||||||
|
splitted_arg = arg[/^\w+/, 0]
|
||||||
|
SPECIFIC_MATCHER[splitted_arg]
|
||||||
|
end
|
||||||
|
|
||||||
|
def message(node, matcher)
|
||||||
|
format(MSG,
|
||||||
|
good_matcher: good_matcher(node, matcher),
|
||||||
|
bad_matcher: node.method_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def good_matcher(node, matcher)
|
||||||
|
node.method_name
|
||||||
|
.to_s
|
||||||
|
.gsub(/selector|css/, matcher.to_s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module Capybara
|
||||||
|
# Checks for boolean visibility in Capybara finders.
|
||||||
|
#
|
||||||
|
# Capybara lets you find elements that match a certain visibility using
|
||||||
|
# the `:visible` option. `:visible` accepts both boolean and symbols as
|
||||||
|
# values, however using booleans can have unwanted effects. `visible:
|
||||||
|
# false` does not find just invisible elements, but both visible and
|
||||||
|
# invisible elements. For expressiveness and clarity, use one of the
|
||||||
|
# symbol values, `:all`, `:hidden` or `:visible`.
|
||||||
|
# Read more in
|
||||||
|
# https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all[the documentation].
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# # bad
|
||||||
|
# expect(page).to have_selector('.foo', visible: false)
|
||||||
|
# expect(page).to have_css('.foo', visible: true)
|
||||||
|
# expect(page).to have_link('my link', visible: false)
|
||||||
|
#
|
||||||
|
# # good
|
||||||
|
# expect(page).to have_selector('.foo', visible: :visible)
|
||||||
|
# expect(page).to have_css('.foo', visible: :all)
|
||||||
|
# expect(page).to have_link('my link', visible: :hidden)
|
||||||
|
#
|
||||||
|
class VisibilityMatcher < ::RuboCop::Cop::Base
|
||||||
|
MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
|
||||||
|
MSG_TRUE = 'Use `:visible` instead of `true`.'
|
||||||
|
CAPYBARA_MATCHER_METHODS = %w[
|
||||||
|
button
|
||||||
|
checked_field
|
||||||
|
css
|
||||||
|
field
|
||||||
|
link
|
||||||
|
select
|
||||||
|
selector
|
||||||
|
table
|
||||||
|
unchecked_field
|
||||||
|
xpath
|
||||||
|
].flat_map do |element|
|
||||||
|
["have_#{element}".to_sym, "have_no_#{element}".to_sym]
|
||||||
|
end
|
||||||
|
|
||||||
|
RESTRICT_ON_SEND = CAPYBARA_MATCHER_METHODS
|
||||||
|
|
||||||
|
# @!method visible_true?(node)
|
||||||
|
def_node_matcher :visible_true?, <<~PATTERN
|
||||||
|
(send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) true) ...>))
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
# @!method visible_false?(node)
|
||||||
|
def_node_matcher :visible_false?, <<~PATTERN
|
||||||
|
(send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) false) ...>))
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def on_send(node)
|
||||||
|
visible_false?(node) { |arg| add_offense(arg, message: MSG_FALSE) }
|
||||||
|
visible_true?(node) { |arg| add_offense(arg, message: MSG_TRUE) }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def capybara_matcher?(method_name)
|
||||||
|
CAPYBARA_MATCHER_METHODS.include? method_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'capybara/current_path_expectation'
|
||||||
|
require_relative 'capybara/match_style'
|
||||||
|
require_relative 'capybara/negation_matcher'
|
||||||
|
require_relative 'capybara/specific_actions'
|
||||||
|
require_relative 'capybara/specific_finders'
|
||||||
|
require_relative 'capybara/specific_matcher'
|
||||||
|
require_relative 'capybara/visibility_matcher'
|
||||||
@ -1,14 +0,0 @@
|
|||||||
#
|
|
||||||
# Configuration of obsolete/deprecated cops used by `ConfigObsoletion`.
|
|
||||||
#
|
|
||||||
# See: https://docs.rubocop.org/rubocop/extensions.html#config-obsoletions
|
|
||||||
#
|
|
||||||
|
|
||||||
# Cop parameters that have been changed
|
|
||||||
# Can be treated as a warning instead of a failure with `severity: warning`
|
|
||||||
changed_parameters:
|
|
||||||
- cops:
|
|
||||||
- RSpec/VariableName
|
|
||||||
parameters: IgnoredPatterns
|
|
||||||
alternative: AllowedPatterns
|
|
||||||
severity: warning
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module RuboCop
|
|
||||||
module Cop
|
|
||||||
module RSpec
|
|
||||||
module Capybara
|
|
||||||
# Checks that no expectations are set on Capybara's `current_path`.
|
|
||||||
#
|
|
||||||
# The
|
|
||||||
# https://www.rubydoc.info/github/teamcapybara/capybara/master/Capybara/RSpecMatchers#have_current_path-instance_method[`have_current_path` matcher]
|
|
||||||
# should be used on `page` to set expectations on Capybara's
|
|
||||||
# current path, since it uses
|
|
||||||
# https://github.com/teamcapybara/capybara/blob/master/README.md#asynchronous-javascript-ajax-and-friends[Capybara's waiting functionality]
|
|
||||||
# which ensures that preceding actions (like `click_link`) have
|
|
||||||
# completed.
|
|
||||||
#
|
|
||||||
# This cop does not support autocorrection in some cases.
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# # bad
|
|
||||||
# expect(current_path).to eq('/callback')
|
|
||||||
#
|
|
||||||
# # good
|
|
||||||
# expect(page).to have_current_path('/callback')
|
|
||||||
#
|
|
||||||
# # bad (does not support autocorrection)
|
|
||||||
# expect(page.current_path).to match(variable)
|
|
||||||
#
|
|
||||||
# # good
|
|
||||||
# expect(page).to have_current_path('/callback')
|
|
||||||
#
|
|
||||||
class CurrentPathExpectation < ::RuboCop::Cop::Base
|
|
||||||
extend AutoCorrector
|
|
||||||
|
|
||||||
MSG = 'Do not set an RSpec expectation on `current_path` in ' \
|
|
||||||
'Capybara feature specs - instead, use the ' \
|
|
||||||
'`have_current_path` matcher on `page`'
|
|
||||||
|
|
||||||
RESTRICT_ON_SEND = %i[expect].freeze
|
|
||||||
|
|
||||||
# @!method expectation_set_on_current_path(node)
|
|
||||||
def_node_matcher :expectation_set_on_current_path, <<-PATTERN
|
|
||||||
(send nil? :expect (send {(send nil? :page) nil?} :current_path))
|
|
||||||
PATTERN
|
|
||||||
|
|
||||||
# Supported matchers: eq(...) / match(/regexp/) / match('regexp')
|
|
||||||
# @!method as_is_matcher(node)
|
|
||||||
def_node_matcher :as_is_matcher, <<-PATTERN
|
|
||||||
(send
|
|
||||||
#expectation_set_on_current_path ${:to :to_not :not_to}
|
|
||||||
${(send nil? :eq ...) (send nil? :match (regexp ...))})
|
|
||||||
PATTERN
|
|
||||||
|
|
||||||
# @!method regexp_str_matcher(node)
|
|
||||||
def_node_matcher :regexp_str_matcher, <<-PATTERN
|
|
||||||
(send
|
|
||||||
#expectation_set_on_current_path ${:to :to_not :not_to}
|
|
||||||
$(send nil? :match (str $_)))
|
|
||||||
PATTERN
|
|
||||||
|
|
||||||
def self.autocorrect_incompatible_with
|
|
||||||
[Style::TrailingCommaInArguments]
|
|
||||||
end
|
|
||||||
|
|
||||||
def on_send(node)
|
|
||||||
expectation_set_on_current_path(node) do
|
|
||||||
add_offense(node.loc.selector) do |corrector|
|
|
||||||
next unless node.chained?
|
|
||||||
|
|
||||||
autocorrect(corrector, node)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def autocorrect(corrector, node)
|
|
||||||
as_is_matcher(node.parent) do |to_sym, matcher_node|
|
|
||||||
rewrite_expectation(corrector, node, to_sym, matcher_node)
|
|
||||||
end
|
|
||||||
|
|
||||||
regexp_str_matcher(node.parent) do |to_sym, matcher_node, regexp|
|
|
||||||
rewrite_expectation(corrector, node, to_sym, matcher_node)
|
|
||||||
convert_regexp_str_to_literal(corrector, matcher_node, regexp)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def rewrite_expectation(corrector, node, to_symbol, matcher_node)
|
|
||||||
current_path_node = node.first_argument
|
|
||||||
corrector.replace(current_path_node, 'page')
|
|
||||||
corrector.replace(node.parent.loc.selector, 'to')
|
|
||||||
matcher_method = if to_symbol == :to
|
|
||||||
'have_current_path'
|
|
||||||
else
|
|
||||||
'have_no_current_path'
|
|
||||||
end
|
|
||||||
corrector.replace(matcher_node.loc.selector, matcher_method)
|
|
||||||
add_ignore_query_options(corrector, node)
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_regexp_str_to_literal(corrector, matcher_node, regexp_str)
|
|
||||||
str_node = matcher_node.first_argument
|
|
||||||
regexp_expr = Regexp.new(regexp_str).inspect
|
|
||||||
corrector.replace(str_node, regexp_expr)
|
|
||||||
end
|
|
||||||
|
|
||||||
# `have_current_path` with no options will include the querystring
|
|
||||||
# while `page.current_path` does not.
|
|
||||||
# This ensures the option `ignore_query: true` is added
|
|
||||||
# except when the expectation is a regexp or string
|
|
||||||
def add_ignore_query_options(corrector, node)
|
|
||||||
expectation_node = node.parent.last_argument
|
|
||||||
expectation_last_child = expectation_node.children.last
|
|
||||||
return if %i[regexp str].include?(expectation_last_child.type)
|
|
||||||
|
|
||||||
corrector.insert_after(
|
|
||||||
expectation_last_child,
|
|
||||||
', ignore_query: true'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module RuboCop
|
|
||||||
module Cop
|
|
||||||
module RSpec
|
|
||||||
module Capybara
|
|
||||||
# Checks for usage of deprecated style methods.
|
|
||||||
#
|
|
||||||
# @example when using `assert_style`
|
|
||||||
# # bad
|
|
||||||
# page.find(:css, '#first').assert_style(display: 'block')
|
|
||||||
#
|
|
||||||
# # good
|
|
||||||
# page.find(:css, '#first').assert_matches_style(display: 'block')
|
|
||||||
#
|
|
||||||
# @example when using `has_style?`
|
|
||||||
# # bad
|
|
||||||
# expect(page.find(:css, 'first')
|
|
||||||
# .has_style?(display: 'block')).to be true
|
|
||||||
#
|
|
||||||
# # good
|
|
||||||
# expect(page.find(:css, 'first')
|
|
||||||
# .matches_style?(display: 'block')).to be true
|
|
||||||
#
|
|
||||||
# @example when using `have_style`
|
|
||||||
# # bad
|
|
||||||
# expect(page).to have_style(display: 'block')
|
|
||||||
#
|
|
||||||
# # good
|
|
||||||
# expect(page).to match_style(display: 'block')
|
|
||||||
#
|
|
||||||
class MatchStyle < Base
|
|
||||||
extend AutoCorrector
|
|
||||||
|
|
||||||
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
|
||||||
RESTRICT_ON_SEND = %i[assert_style has_style? have_style].freeze
|
|
||||||
PREFERRED_METHOD = {
|
|
||||||
'assert_style' => 'assert_matches_style',
|
|
||||||
'has_style?' => 'matches_style?',
|
|
||||||
'have_style' => 'match_style'
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
def on_send(node)
|
|
||||||
method_node = node.loc.selector
|
|
||||||
add_offense(method_node) do |corrector|
|
|
||||||
corrector.replace(method_node,
|
|
||||||
PREFERRED_METHOD[method_node.source])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def message(node)
|
|
||||||
format(MSG, good: PREFERRED_METHOD[node.source], bad: node.source)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
# 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 < ::RuboCop::Cop::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
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module RuboCop
|
|
||||||
module Cop
|
|
||||||
module RSpec
|
|
||||||
module Capybara
|
|
||||||
# Checks for there is a more specific actions offered by Capybara.
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
#
|
|
||||||
# # bad
|
|
||||||
# find('a').click
|
|
||||||
# find('button.cls').click
|
|
||||||
# find('a', exact_text: 'foo').click
|
|
||||||
# find('div button').click
|
|
||||||
#
|
|
||||||
# # good
|
|
||||||
# click_link
|
|
||||||
# click_button(class: 'cls')
|
|
||||||
# click_link(exact_text: 'foo')
|
|
||||||
# find('div').click_button
|
|
||||||
#
|
|
||||||
class SpecificActions < ::RuboCop::Cop::Base
|
|
||||||
MSG = "Prefer `%<good_action>s` over `find('%<selector>s').click`."
|
|
||||||
RESTRICT_ON_SEND = %i[click].freeze
|
|
||||||
SPECIFIC_ACTION = {
|
|
||||||
'button' => 'button',
|
|
||||||
'a' => 'link'
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
# @!method click_on_selector(node)
|
|
||||||
def_node_matcher :click_on_selector, <<-PATTERN
|
|
||||||
(send _ :find (str $_) ...)
|
|
||||||
PATTERN
|
|
||||||
|
|
||||||
def on_send(node)
|
|
||||||
click_on_selector(node.receiver) do |arg|
|
|
||||||
next unless supported_selector?(arg)
|
|
||||||
# Always check the last selector in the case of multiple selectors
|
|
||||||
# separated by whitespace.
|
|
||||||
# because the `.click` is executed on the element to
|
|
||||||
# which the last selector points.
|
|
||||||
next unless (selector = last_selector(arg))
|
|
||||||
next unless (action = specific_action(selector))
|
|
||||||
next unless CapybaraHelp.specific_option?(node.receiver, arg,
|
|
||||||
action)
|
|
||||||
next unless CapybaraHelp.specific_pseudo_classes?(arg)
|
|
||||||
|
|
||||||
range = offense_range(node, node.receiver)
|
|
||||||
add_offense(range, message: message(action, selector))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def specific_action(selector)
|
|
||||||
SPECIFIC_ACTION[last_selector(selector)]
|
|
||||||
end
|
|
||||||
|
|
||||||
def supported_selector?(selector)
|
|
||||||
!selector.match?(/[>,+~]/)
|
|
||||||
end
|
|
||||||
|
|
||||||
def last_selector(arg)
|
|
||||||
arg.split.last[/^\w+/, 0]
|
|
||||||
end
|
|
||||||
|
|
||||||
def offense_range(node, receiver)
|
|
||||||
receiver.loc.selector.with(end_pos: node.loc.expression.end_pos)
|
|
||||||
end
|
|
||||||
|
|
||||||
def message(action, selector)
|
|
||||||
format(MSG,
|
|
||||||
good_action: good_action(action),
|
|
||||||
selector: selector)
|
|
||||||
end
|
|
||||||
|
|
||||||
def good_action(action)
|
|
||||||
"click_#{action}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module RuboCop
|
|
||||||
module Cop
|
|
||||||
module RSpec
|
|
||||||
module Capybara
|
|
||||||
# Checks if there is a more specific finder offered by Capybara.
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# # bad
|
|
||||||
# find('#some-id')
|
|
||||||
# find('[visible][id=some-id]')
|
|
||||||
#
|
|
||||||
# # good
|
|
||||||
# find_by_id('some-id')
|
|
||||||
# find_by_id('some-id', visible: true)
|
|
||||||
#
|
|
||||||
class SpecificFinders < ::RuboCop::Cop::Base
|
|
||||||
extend AutoCorrector
|
|
||||||
|
|
||||||
include RangeHelp
|
|
||||||
|
|
||||||
MSG = 'Prefer `find_by` over `find`.'
|
|
||||||
RESTRICT_ON_SEND = %i[find].freeze
|
|
||||||
|
|
||||||
# @!method find_argument(node)
|
|
||||||
def_node_matcher :find_argument, <<~PATTERN
|
|
||||||
(send _ :find (str $_) ...)
|
|
||||||
PATTERN
|
|
||||||
|
|
||||||
def on_send(node)
|
|
||||||
find_argument(node) do |arg|
|
|
||||||
next if CssSelector.multiple_selectors?(arg)
|
|
||||||
|
|
||||||
on_attr(node, arg) if attribute?(arg)
|
|
||||||
on_id(node, arg) if CssSelector.id?(arg)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def on_attr(node, arg)
|
|
||||||
return unless (id = CssSelector.attributes(arg)['id'])
|
|
||||||
|
|
||||||
register_offense(node, replaced_arguments(arg, id))
|
|
||||||
end
|
|
||||||
|
|
||||||
def on_id(node, arg)
|
|
||||||
register_offense(node, "'#{arg.to_s.delete('#')}'")
|
|
||||||
end
|
|
||||||
|
|
||||||
def attribute?(arg)
|
|
||||||
CssSelector.attribute?(arg) &&
|
|
||||||
CssSelector.common_attributes?(arg)
|
|
||||||
end
|
|
||||||
|
|
||||||
def register_offense(node, arg_replacement)
|
|
||||||
add_offense(offense_range(node)) do |corrector|
|
|
||||||
corrector.replace(node.loc.selector, 'find_by_id')
|
|
||||||
corrector.replace(node.first_argument.loc.expression,
|
|
||||||
arg_replacement)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def replaced_arguments(arg, id)
|
|
||||||
options = to_options(CssSelector.attributes(arg))
|
|
||||||
options.empty? ? id : "#{id}, #{options}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_options(attrs)
|
|
||||||
attrs.each.map do |key, value|
|
|
||||||
next if key == 'id'
|
|
||||||
|
|
||||||
"#{key}: #{value}"
|
|
||||||
end.compact.join(', ')
|
|
||||||
end
|
|
||||||
|
|
||||||
def offense_range(node)
|
|
||||||
range_between(node.loc.selector.begin_pos, end_pos(node))
|
|
||||||
end
|
|
||||||
|
|
||||||
def end_pos(node)
|
|
||||||
if node.loc.end
|
|
||||||
node.loc.end.end_pos
|
|
||||||
else
|
|
||||||
node.loc.expression.end_pos
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module RuboCop
|
|
||||||
module Cop
|
|
||||||
module RSpec
|
|
||||||
module Capybara
|
|
||||||
# Checks for there is a more specific matcher offered by Capybara.
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
#
|
|
||||||
# # bad
|
|
||||||
# expect(page).to have_selector('button')
|
|
||||||
# expect(page).to have_no_selector('button.cls')
|
|
||||||
# expect(page).to have_css('button')
|
|
||||||
# expect(page).to have_no_css('a.cls', href: 'http://example.com')
|
|
||||||
# expect(page).to have_css('table.cls')
|
|
||||||
# expect(page).to have_css('select')
|
|
||||||
# expect(page).to have_css('input', exact_text: 'foo')
|
|
||||||
#
|
|
||||||
# # good
|
|
||||||
# expect(page).to have_button
|
|
||||||
# expect(page).to have_no_button(class: 'cls')
|
|
||||||
# expect(page).to have_button
|
|
||||||
# expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
|
|
||||||
# expect(page).to have_table(class: 'cls')
|
|
||||||
# expect(page).to have_select
|
|
||||||
# expect(page).to have_field('foo')
|
|
||||||
#
|
|
||||||
class SpecificMatcher < ::RuboCop::Cop::Base
|
|
||||||
MSG = 'Prefer `%<good_matcher>s` over `%<bad_matcher>s`.'
|
|
||||||
RESTRICT_ON_SEND = %i[have_selector have_no_selector have_css
|
|
||||||
have_no_css].freeze
|
|
||||||
SPECIFIC_MATCHER = {
|
|
||||||
'button' => 'button',
|
|
||||||
'a' => 'link',
|
|
||||||
'table' => 'table',
|
|
||||||
'select' => 'select',
|
|
||||||
'input' => 'field'
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
# @!method first_argument(node)
|
|
||||||
def_node_matcher :first_argument, <<-PATTERN
|
|
||||||
(send nil? _ (str $_) ... )
|
|
||||||
PATTERN
|
|
||||||
|
|
||||||
def on_send(node)
|
|
||||||
first_argument(node) do |arg|
|
|
||||||
next unless (matcher = specific_matcher(arg))
|
|
||||||
next if CssSelector.multiple_selectors?(arg)
|
|
||||||
next unless CapybaraHelp.specific_option?(node, arg, matcher)
|
|
||||||
next unless CapybaraHelp.specific_pseudo_classes?(arg)
|
|
||||||
|
|
||||||
add_offense(node, message: message(node, matcher))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def specific_matcher(arg)
|
|
||||||
splitted_arg = arg[/^\w+/, 0]
|
|
||||||
SPECIFIC_MATCHER[splitted_arg]
|
|
||||||
end
|
|
||||||
|
|
||||||
def message(node, matcher)
|
|
||||||
format(MSG,
|
|
||||||
good_matcher: good_matcher(node, matcher),
|
|
||||||
bad_matcher: node.method_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def good_matcher(node, matcher)
|
|
||||||
node.method_name
|
|
||||||
.to_s
|
|
||||||
.gsub(/selector|css/, matcher.to_s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module RuboCop
|
|
||||||
module Cop
|
|
||||||
module RSpec
|
|
||||||
module Capybara
|
|
||||||
# Checks for boolean visibility in Capybara finders.
|
|
||||||
#
|
|
||||||
# Capybara lets you find elements that match a certain visibility using
|
|
||||||
# the `:visible` option. `:visible` accepts both boolean and symbols as
|
|
||||||
# values, however using booleans can have unwanted effects. `visible:
|
|
||||||
# false` does not find just invisible elements, but both visible and
|
|
||||||
# invisible elements. For expressiveness and clarity, use one of the
|
|
||||||
# symbol values, `:all`, `:hidden` or `:visible`.
|
|
||||||
# Read more in
|
|
||||||
# https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all[the documentation].
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# # bad
|
|
||||||
# expect(page).to have_selector('.foo', visible: false)
|
|
||||||
# expect(page).to have_css('.foo', visible: true)
|
|
||||||
# expect(page).to have_link('my link', visible: false)
|
|
||||||
#
|
|
||||||
# # good
|
|
||||||
# expect(page).to have_selector('.foo', visible: :visible)
|
|
||||||
# expect(page).to have_css('.foo', visible: :all)
|
|
||||||
# expect(page).to have_link('my link', visible: :hidden)
|
|
||||||
#
|
|
||||||
class VisibilityMatcher < ::RuboCop::Cop::Base
|
|
||||||
MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
|
|
||||||
MSG_TRUE = 'Use `:visible` instead of `true`.'
|
|
||||||
CAPYBARA_MATCHER_METHODS = %w[
|
|
||||||
button
|
|
||||||
checked_field
|
|
||||||
css
|
|
||||||
field
|
|
||||||
link
|
|
||||||
select
|
|
||||||
selector
|
|
||||||
table
|
|
||||||
unchecked_field
|
|
||||||
xpath
|
|
||||||
].flat_map do |element|
|
|
||||||
["have_#{element}".to_sym, "have_no_#{element}".to_sym]
|
|
||||||
end
|
|
||||||
|
|
||||||
RESTRICT_ON_SEND = CAPYBARA_MATCHER_METHODS
|
|
||||||
|
|
||||||
# @!method visible_true?(node)
|
|
||||||
def_node_matcher :visible_true?, <<~PATTERN
|
|
||||||
(send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) true) ...>))
|
|
||||||
PATTERN
|
|
||||||
|
|
||||||
# @!method visible_false?(node)
|
|
||||||
def_node_matcher :visible_false?, <<~PATTERN
|
|
||||||
(send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) false) ...>))
|
|
||||||
PATTERN
|
|
||||||
|
|
||||||
def on_send(node)
|
|
||||||
visible_false?(node) { |arg| add_offense(arg, message: MSG_FALSE) }
|
|
||||||
visible_true?(node) { |arg| add_offense(arg, message: MSG_TRUE) }
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def capybara_matcher?(method_name)
|
|
||||||
CAPYBARA_MATCHER_METHODS.include? method_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module RuboCop
|
|
||||||
module Cop
|
|
||||||
module RSpec
|
|
||||||
# Help methods for capybara.
|
|
||||||
module CapybaraHelp
|
|
||||||
module_function
|
|
||||||
|
|
||||||
# @param node [RuboCop::AST::SendNode]
|
|
||||||
# @param locator [String]
|
|
||||||
# @param element [String]
|
|
||||||
# @return [Boolean]
|
|
||||||
def specific_option?(node, locator, element)
|
|
||||||
attrs = CssSelector.attributes(locator).keys
|
|
||||||
return false unless replaceable_element?(node, element, attrs)
|
|
||||||
|
|
||||||
attrs.all? do |attr|
|
|
||||||
CssSelector.specific_options?(element, attr)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param locator [String]
|
|
||||||
# @return [Boolean]
|
|
||||||
def specific_pseudo_classes?(locator)
|
|
||||||
CssSelector.pseudo_classes(locator).all? do |pseudo_class|
|
|
||||||
replaceable_pseudo_class?(pseudo_class, locator)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param pseudo_class [String]
|
|
||||||
# @param locator [String]
|
|
||||||
# @return [Boolean]
|
|
||||||
def replaceable_pseudo_class?(pseudo_class, locator)
|
|
||||||
return false unless CssSelector.specific_pesudo_classes?(pseudo_class)
|
|
||||||
|
|
||||||
case pseudo_class
|
|
||||||
when 'not()' then replaceable_pseudo_class_not?(locator)
|
|
||||||
else true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param locator [String]
|
|
||||||
# @return [Boolean]
|
|
||||||
def replaceable_pseudo_class_not?(locator)
|
|
||||||
locator.scan(/not\(.*?\)/).all? do |negation|
|
|
||||||
CssSelector.attributes(negation).values.all? do |v|
|
|
||||||
v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param node [RuboCop::AST::SendNode]
|
|
||||||
# @param element [String]
|
|
||||||
# @param attrs [Array<String>]
|
|
||||||
# @return [Boolean]
|
|
||||||
def replaceable_element?(node, element, attrs)
|
|
||||||
case element
|
|
||||||
when 'link' then replaceable_to_link?(node, attrs)
|
|
||||||
else true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param node [RuboCop::AST::SendNode]
|
|
||||||
# @param attrs [Array<String>]
|
|
||||||
# @return [Boolean]
|
|
||||||
def replaceable_to_link?(node, attrs)
|
|
||||||
include_option?(node, :href) || attrs.include?('href')
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param node [RuboCop::AST::SendNode]
|
|
||||||
# @param option [Symbol]
|
|
||||||
# @return [Boolean]
|
|
||||||
def include_option?(node, option)
|
|
||||||
node.each_descendant(:sym).find { |opt| opt.value == option }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module RuboCop
|
|
||||||
module Cop
|
|
||||||
module RSpec
|
|
||||||
# Helps parsing css selector.
|
|
||||||
module CssSelector
|
|
||||||
COMMON_OPTIONS = %w[
|
|
||||||
above below left_of right_of near count minimum maximum between text
|
|
||||||
id class style visible obscured exact exact_text normalize_ws match
|
|
||||||
wait filter_set focused
|
|
||||||
].freeze
|
|
||||||
SPECIFIC_OPTIONS = {
|
|
||||||
'button' => (
|
|
||||||
COMMON_OPTIONS + %w[disabled name value title type]
|
|
||||||
).freeze,
|
|
||||||
'link' => (
|
|
||||||
COMMON_OPTIONS + %w[href alt title download]
|
|
||||||
).freeze,
|
|
||||||
'table' => (
|
|
||||||
COMMON_OPTIONS + %w[
|
|
||||||
caption with_cols cols with_rows rows
|
|
||||||
]
|
|
||||||
).freeze,
|
|
||||||
'select' => (
|
|
||||||
COMMON_OPTIONS + %w[
|
|
||||||
disabled name placeholder options enabled_options
|
|
||||||
disabled_options selected with_selected multiple with_options
|
|
||||||
]
|
|
||||||
).freeze,
|
|
||||||
'field' => (
|
|
||||||
COMMON_OPTIONS + %w[
|
|
||||||
checked unchecked disabled valid name placeholder
|
|
||||||
validation_message readonly with type multiple
|
|
||||||
]
|
|
||||||
).freeze
|
|
||||||
}.freeze
|
|
||||||
SPECIFIC_PSEUDO_CLASSES = %w[
|
|
||||||
not() disabled enabled checked unchecked
|
|
||||||
].freeze
|
|
||||||
|
|
||||||
module_function
|
|
||||||
|
|
||||||
# @param element [String]
|
|
||||||
# @param attribute [String]
|
|
||||||
# @return [Boolean]
|
|
||||||
# @example
|
|
||||||
# specific_pesudo_classes?('button', 'name') # => true
|
|
||||||
# specific_pesudo_classes?('link', 'invalid') # => false
|
|
||||||
def specific_options?(element, attribute)
|
|
||||||
SPECIFIC_OPTIONS.fetch(element, []).include?(attribute)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param pseudo_class [String]
|
|
||||||
# @return [Boolean]
|
|
||||||
# @example
|
|
||||||
# specific_pesudo_classes?('disabled') # => true
|
|
||||||
# specific_pesudo_classes?('first-of-type') # => false
|
|
||||||
def specific_pesudo_classes?(pseudo_class)
|
|
||||||
SPECIFIC_PSEUDO_CLASSES.include?(pseudo_class)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param selector [String]
|
|
||||||
# @return [Boolean]
|
|
||||||
# @example
|
|
||||||
# id?('#some-id') # => true
|
|
||||||
# id?('.some-class') # => false
|
|
||||||
def id?(selector)
|
|
||||||
selector.start_with?('#')
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param selector [String]
|
|
||||||
# @return [Boolean]
|
|
||||||
# @example
|
|
||||||
# attribute?('[attribute]') # => true
|
|
||||||
# attribute?('attribute') # => false
|
|
||||||
def attribute?(selector)
|
|
||||||
selector.start_with?('[')
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param selector [String]
|
|
||||||
# @return [Array<String>]
|
|
||||||
# @example
|
|
||||||
# attributes('a[foo-bar_baz]') # => {"foo-bar_baz=>true}
|
|
||||||
# attributes('button[foo][bar]') # => {"foo"=>true, "bar"=>true}
|
|
||||||
# attributes('table[foo=bar]') # => {"foo"=>"'bar'"}
|
|
||||||
def attributes(selector)
|
|
||||||
selector.scan(/\[(.*?)\]/).flatten.to_h do |attr|
|
|
||||||
key, value = attr.split('=')
|
|
||||||
[key, normalize_value(value)]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param selector [String]
|
|
||||||
# @return [Boolean]
|
|
||||||
# @example
|
|
||||||
# common_attributes?('a[focused]') # => true
|
|
||||||
# common_attributes?('button[focused][visible]') # => true
|
|
||||||
# common_attributes?('table[id=some-id]') # => true
|
|
||||||
# common_attributes?('h1[invalid]') # => false
|
|
||||||
def common_attributes?(selector)
|
|
||||||
attributes(selector).keys.difference(COMMON_OPTIONS).none?
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param selector [String]
|
|
||||||
# @return [Array<String>]
|
|
||||||
# @example
|
|
||||||
# pseudo_classes('button:not([disabled])') # => ['not()']
|
|
||||||
# pseudo_classes('a:enabled:not([valid])') # => ['enabled', 'not()']
|
|
||||||
def pseudo_classes(selector)
|
|
||||||
# Attributes must be excluded or else the colon in the `href`s URL
|
|
||||||
# will also be picked up as pseudo classes.
|
|
||||||
# "a:not([href='http://example.com']):enabled" => "a:not():enabled"
|
|
||||||
ignored_attribute = selector.gsub(/\[.*?\]/, '')
|
|
||||||
# "a:not():enabled" => ["not()", "enabled"]
|
|
||||||
ignored_attribute.scan(/:([^:]*)/).flatten
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param selector [String]
|
|
||||||
# @return [Boolean]
|
|
||||||
# @example
|
|
||||||
# multiple_selectors?('a.cls b#id') # => true
|
|
||||||
# multiple_selectors?('a.cls') # => false
|
|
||||||
def multiple_selectors?(selector)
|
|
||||||
selector.match?(/[ >,+~]/)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param value [String]
|
|
||||||
# @return [Boolean, String]
|
|
||||||
# @example
|
|
||||||
# normalize_value('true') # => true
|
|
||||||
# normalize_value('false') # => false
|
|
||||||
# normalize_value(nil) # => false
|
|
||||||
# normalize_value("foo") # => "'foo'"
|
|
||||||
def normalize_value(value)
|
|
||||||
case value
|
|
||||||
when 'true' then true
|
|
||||||
when 'false' then false
|
|
||||||
when nil then true
|
|
||||||
else "'#{value}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
23
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rspec-2.18.0/config/obsoletion.yml
vendored
Normal file
23
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rspec-2.18.0/config/obsoletion.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#
|
||||||
|
# Configuration of obsolete/deprecated cops used by `ConfigObsoletion`.
|
||||||
|
#
|
||||||
|
# See: https://docs.rubocop.org/rubocop/extensions.html#config-obsoletions
|
||||||
|
#
|
||||||
|
|
||||||
|
# Cop parameters that have been changed
|
||||||
|
# Can be treated as a warning instead of a failure with `severity: warning`
|
||||||
|
changed_parameters:
|
||||||
|
- cops:
|
||||||
|
- RSpec/VariableName
|
||||||
|
parameters: IgnoredPatterns
|
||||||
|
alternative: AllowedPatterns
|
||||||
|
severity: warning
|
||||||
|
|
||||||
|
renamed:
|
||||||
|
RSpec/Capybara/CurrentPathExpectation: Capybara/CurrentPathExpectation
|
||||||
|
RSpec/Capybara/MatchStyle: Capybara/MatchStyle
|
||||||
|
RSpec/Capybara/NegationMatcher: Capybara/NegationMatcher
|
||||||
|
RSpec/Capybara/SpecificActions: Capybara/SpecificActions
|
||||||
|
RSpec/Capybara/SpecificFinders: Capybara/SpecificFinders
|
||||||
|
RSpec/Capybara/SpecificMatcher: Capybara/SpecificMatcher
|
||||||
|
RSpec/Capybara/VisibilityMatcher: Capybara/VisibilityMatcher
|
||||||
@ -4,6 +4,7 @@ require 'pathname'
|
|||||||
require 'yaml'
|
require 'yaml'
|
||||||
|
|
||||||
require 'rubocop'
|
require 'rubocop'
|
||||||
|
require 'rubocop-capybara'
|
||||||
|
|
||||||
require_relative 'rubocop/rspec'
|
require_relative 'rubocop/rspec'
|
||||||
require_relative 'rubocop/rspec/inject'
|
require_relative 'rubocop/rspec/inject'
|
||||||
@ -17,8 +18,6 @@ require_relative 'rubocop/rspec/language'
|
|||||||
|
|
||||||
require_relative 'rubocop/rspec/factory_bot/language'
|
require_relative 'rubocop/rspec/factory_bot/language'
|
||||||
|
|
||||||
require_relative 'rubocop/cop/rspec/mixin/capybara_help'
|
|
||||||
require_relative 'rubocop/cop/rspec/mixin/css_selector'
|
|
||||||
require_relative 'rubocop/cop/rspec/mixin/final_end_location'
|
require_relative 'rubocop/cop/rspec/mixin/final_end_location'
|
||||||
require_relative 'rubocop/cop/rspec/mixin/inside_example_group'
|
require_relative 'rubocop/cop/rspec/mixin/inside_example_group'
|
||||||
require_relative 'rubocop/cop/rspec/mixin/metadata'
|
require_relative 'rubocop/cop/rspec/mixin/metadata'
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module RSpec
|
||||||
|
module Capybara
|
||||||
|
# @!parse
|
||||||
|
# # Checks that no expectations are set on Capybara's `current_path`.
|
||||||
|
# #
|
||||||
|
# # The
|
||||||
|
# # https://www.rubydoc.info/github/teamcapybara/capybara/master/Capybara/RSpecMatchers#have_current_path-instance_method[`have_current_path` matcher]
|
||||||
|
# # should be used on `page` to set expectations on Capybara's
|
||||||
|
# # current path, since it uses
|
||||||
|
# # https://github.com/teamcapybara/capybara/blob/master/README.md#asynchronous-javascript-ajax-and-friends[Capybara's waiting functionality]
|
||||||
|
# # which ensures that preceding actions (like `click_link`) have
|
||||||
|
# # completed.
|
||||||
|
# #
|
||||||
|
# # This cop does not support autocorrection in some cases.
|
||||||
|
# #
|
||||||
|
# # @example
|
||||||
|
# # # bad
|
||||||
|
# # expect(current_path).to eq('/callback')
|
||||||
|
# #
|
||||||
|
# # # good
|
||||||
|
# # expect(page).to have_current_path('/callback')
|
||||||
|
# #
|
||||||
|
# # # bad (does not support autocorrection)
|
||||||
|
# # expect(page.current_path).to match(variable)
|
||||||
|
# #
|
||||||
|
# # # good
|
||||||
|
# # expect(page).to have_current_path('/callback')
|
||||||
|
# #
|
||||||
|
# class CurrentPathExpectation < ::RuboCop::Cop::Base; end
|
||||||
|
CurrentPathExpectation =
|
||||||
|
::RuboCop::Cop::Capybara::CurrentPathExpectation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module RSpec
|
||||||
|
module Capybara
|
||||||
|
# @!parse
|
||||||
|
# # Checks for usage of deprecated style methods.
|
||||||
|
# #
|
||||||
|
# # @example when using `assert_style`
|
||||||
|
# # # bad
|
||||||
|
# # page.find(:css, '#first').assert_style(display: 'block')
|
||||||
|
# #
|
||||||
|
# # # good
|
||||||
|
# # page.find(:css, '#first').assert_matches_style(display: 'block')
|
||||||
|
# #
|
||||||
|
# # @example when using `has_style?`
|
||||||
|
# # # bad
|
||||||
|
# # expect(page.find(:css, 'first')
|
||||||
|
# # .has_style?(display: 'block')).to be true
|
||||||
|
# #
|
||||||
|
# # # good
|
||||||
|
# # expect(page.find(:css, 'first')
|
||||||
|
# # .matches_style?(display: 'block')).to be true
|
||||||
|
# #
|
||||||
|
# # @example when using `have_style`
|
||||||
|
# # # bad
|
||||||
|
# # expect(page).to have_style(display: 'block')
|
||||||
|
# #
|
||||||
|
# # # good
|
||||||
|
# # expect(page).to match_style(display: 'block')
|
||||||
|
# #
|
||||||
|
# class MatchStyle < ::RuboCop::Cop::Base; end
|
||||||
|
MatchStyle = ::RuboCop::Cop::Capybara::MatchStyle
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module RSpec
|
||||||
|
module Capybara
|
||||||
|
# @!parse
|
||||||
|
# # 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 < ::RuboCop::Cop::Base; end
|
||||||
|
NegationMatcher = ::RuboCop::Cop::Capybara::NegationMatcher
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module RSpec
|
||||||
|
module Capybara
|
||||||
|
# @!parse
|
||||||
|
# # Checks for there is a more specific actions offered by Capybara.
|
||||||
|
# #
|
||||||
|
# # @example
|
||||||
|
# #
|
||||||
|
# # # bad
|
||||||
|
# # find('a').click
|
||||||
|
# # find('button.cls').click
|
||||||
|
# # find('a', exact_text: 'foo').click
|
||||||
|
# # find('div button').click
|
||||||
|
# #
|
||||||
|
# # # good
|
||||||
|
# # click_link
|
||||||
|
# # click_button(class: 'cls')
|
||||||
|
# # click_link(exact_text: 'foo')
|
||||||
|
# # find('div').click_button
|
||||||
|
# #
|
||||||
|
# class SpecificActions < ::RuboCop::Cop::Base; end
|
||||||
|
SpecificActions = ::RuboCop::Cop::Capybara::SpecificActions
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module RSpec
|
||||||
|
module Capybara
|
||||||
|
# @!parse
|
||||||
|
# # Checks if there is a more specific finder offered by Capybara.
|
||||||
|
# #
|
||||||
|
# # @example
|
||||||
|
# # # bad
|
||||||
|
# # find('#some-id')
|
||||||
|
# # find('[visible][id=some-id]')
|
||||||
|
# #
|
||||||
|
# # # good
|
||||||
|
# # find_by_id('some-id')
|
||||||
|
# # find_by_id('some-id', visible: true)
|
||||||
|
# #
|
||||||
|
# class SpecificFinders < ::RuboCop::Cop::Base; end
|
||||||
|
SpecificFinders = ::RuboCop::Cop::Capybara::SpecificFinders
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module RSpec
|
||||||
|
module Capybara
|
||||||
|
# @!parse
|
||||||
|
# # Checks for there is a more specific matcher offered by Capybara.
|
||||||
|
# #
|
||||||
|
# # @example
|
||||||
|
# #
|
||||||
|
# # # bad
|
||||||
|
# # expect(page).to have_selector('button')
|
||||||
|
# # expect(page).to have_no_selector('button.cls')
|
||||||
|
# # expect(page).to have_css('button')
|
||||||
|
# # expect(page).to have_no_css('a.cls', href: 'http://example.com')
|
||||||
|
# # expect(page).to have_css('table.cls')
|
||||||
|
# # expect(page).to have_css('select')
|
||||||
|
# # expect(page).to have_css('input', exact_text: 'foo')
|
||||||
|
# #
|
||||||
|
# # # good
|
||||||
|
# # expect(page).to have_button
|
||||||
|
# # expect(page).to have_no_button(class: 'cls')
|
||||||
|
# # expect(page).to have_button
|
||||||
|
# # expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
|
||||||
|
# # expect(page).to have_table(class: 'cls')
|
||||||
|
# # expect(page).to have_select
|
||||||
|
# # expect(page).to have_field('foo')
|
||||||
|
# #
|
||||||
|
# class SpecificMatcher < ::RuboCop::Cop::Base; end
|
||||||
|
SpecificMatcher = ::RuboCop::Cop::Capybara::SpecificMatcher
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module RSpec
|
||||||
|
module Capybara
|
||||||
|
# @!parse
|
||||||
|
# # Checks for boolean visibility in Capybara finders.
|
||||||
|
# #
|
||||||
|
# # Capybara lets you find elements that match a certain visibility
|
||||||
|
# # using the `:visible` option. `:visible` accepts both boolean and
|
||||||
|
# # symbols as values, however using booleans can have unwanted
|
||||||
|
# # effects. `visible: false` does not find just invisible elements,
|
||||||
|
# # but both visible and invisible elements. For expressiveness and
|
||||||
|
# # clarity, use one of the # symbol values, `:all`, `:hidden` or
|
||||||
|
# # `:visible`.
|
||||||
|
# # Read more in
|
||||||
|
# # https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all[the documentation].
|
||||||
|
# #
|
||||||
|
# # @example
|
||||||
|
# # # bad
|
||||||
|
# # expect(page).to have_selector('.foo', visible: false)
|
||||||
|
# # expect(page).to have_css('.foo', visible: true)
|
||||||
|
# # expect(page).to have_link('my link', visible: false)
|
||||||
|
# #
|
||||||
|
# # # good
|
||||||
|
# # expect(page).to have_selector('.foo', visible: :visible)
|
||||||
|
# # expect(page).to have_css('.foo', visible: :all)
|
||||||
|
# # expect(page).to have_link('my link', visible: :hidden)
|
||||||
|
# #
|
||||||
|
# class VisibilityMatcher < ::RuboCop::Cop::Base; end
|
||||||
|
VisibilityMatcher = ::RuboCop::Cop::Capybara::VisibilityMatcher
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -31,7 +31,11 @@ module RuboCop
|
|||||||
|
|
||||||
# @!method context_method(node)
|
# @!method context_method(node)
|
||||||
def_node_matcher :context_method, <<-PATTERN
|
def_node_matcher :context_method, <<-PATTERN
|
||||||
(block (send #rspec? :context $(str #method_name?) ...) ...)
|
(block
|
||||||
|
(send #rspec? :context
|
||||||
|
${(str #method_name?) (dstr (str #method_name?) ...)}
|
||||||
|
...)
|
||||||
|
...)
|
||||||
PATTERN
|
PATTERN
|
||||||
|
|
||||||
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user