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/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-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-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/ruby-macho-3.0.0/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'
|
||||
|
||||
module RuboCop
|
||||
module RSpec
|
||||
module Capybara
|
||||
# Builds a YAML config file from two config hashes
|
||||
class ConfigFormatter
|
||||
EXTENSION_ROOT_DEPARTMENT = %r{^(RSpec/)}.freeze
|
||||
SUBDEPARTMENTS = %(RSpec/Capybara RSpec/FactoryBot RSpec/Rails)
|
||||
AMENDMENTS = %(Metrics/BlockLength)
|
||||
COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/'
|
||||
EXTENSION_ROOT_DEPARTMENT = %r{^(Capybara/)}.freeze
|
||||
SUBDEPARTMENTS = [].freeze
|
||||
AMENDMENTS = [].freeze
|
||||
COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/'
|
||||
|
||||
def initialize(config, descriptions)
|
||||
@config = config
|
||||
@ -19,9 +19,9 @@ module RuboCop
|
||||
def dump
|
||||
YAML.dump(unified_config)
|
||||
.gsub(EXTENSION_ROOT_DEPARTMENT, "\n\\1")
|
||||
.gsub(*AMENDMENTS, "\n\\0")
|
||||
.gsub(/^(\s+)- /, '\1 - ')
|
||||
.gsub('"~"', '~')
|
||||
# .gsub(*AMENDMENTS, "\n\\0")
|
||||
end
|
||||
|
||||
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 'rubocop'
|
||||
require 'rubocop-capybara'
|
||||
|
||||
require_relative 'rubocop/rspec'
|
||||
require_relative 'rubocop/rspec/inject'
|
||||
@ -17,8 +18,6 @@ require_relative 'rubocop/rspec/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/inside_example_group'
|
||||
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)
|
||||
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
|
||||
|
||||
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