rubocop-capybara 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a9046dc7fd875441eeb556b848c7fc36099c2ab694b12a6cf5834ff191937a16
4
+ data.tar.gz: 354393ab09a0849d1c4c7398116827627c5b385e377f5337a135d3d5ff338b08
5
+ SHA512:
6
+ metadata.gz: 7fab06dac5d6ba1989c2c0bf47cd0a674e067baf7265378b8d4493055a1597087a442b79d88b931eadaa0f4ebd5c78f5d1929a116b12afa0d9c20110506651cd
7
+ data.tar.gz: fd8f7cfebf049c450b25e20b76d76fba8ddbb1a50d626644e740e042589cdd2b1d406ea410df1d558439fc2d66a2b37733ce4ebd409f7f83568cd96dea27c61a
data/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ ## Edge (Unreleased)
4
+
5
+ ## 2.17.0 (TBD)
6
+
7
+ - Extracted from `rubocop-rspec` into a separate repository for easier use with Minitest/Cucumber. ([@pirj])
8
+
9
+ ## Previously (see rubocop-rspec's changelist for details)
10
+
11
+ - Fix a false positive for `Capybara/SpecificMatcher` when `have_css("a")` without attribute. ([@ydah])
12
+ - Add new `Capybara/NegationMatcher` cop. ([@ydah])
13
+ - Add new `Capybara/SpecificActions` cop. ([@ydah])
14
+ - Fix an error for `Capybara/SpecificFinders` with no parentheses. ([@ydah])
15
+ - Exclude `have_text` and `have_content` that raise `ArgumentError` with `Capybara/VisibilityMatcher` where `:visible` is an invalid option. ([@ydah])
16
+ - Fix a false negative for `Capybara/VisibilityMatcher` with negative matchers. ([@ydah])
17
+ - Fix a false positive for `Capybara/SpecificMatcher`. ([@ydah])
18
+ - Fix a false negative for `Capybara/SpecificMatcher` for `have_field`. ([@ydah])
19
+ - Fix a false positive for `Capybara/SpecificMatcher` when may not have a `href` by `have_link`. ([@ydah])
20
+ - Add new `Capybara/SpecificFinders` cop. ([@ydah])
21
+ - Fix a false positive for `Capybara/SpecificMatcher` when pseudo-classes. ([@ydah])
22
+ - Fix a false positive for `Capybara/SpecificMatcher`. ([@ydah])
23
+ - Add new `Capybara/SpecificMatcher` cop. ([@ydah])
24
+ - Fix `Capybara/CurrentPathExpectation` autocorrect incompatible with `Style/TrailingCommaInArguments` autocorrect. ([@ydah])
25
+ - Fix `FactoryBot/SyntaxMethods` and `Capybara/FeatureMethods` to inspect shared groups. ([@pirj])
26
+ - Change namespace of several cops (`Capybara/*` -> `RSpec/Capybara/*`, `FactoryBot/*` -> `RSpec/FactoryBot/*`, `Rails/*` -> `RSpec/Rails/*`). ([@pirj], [@bquorning])
27
+ - Expand `Capybara/VisibilityMatcher` to support more than just `have_selector`. ([@twalpole])
28
+ - Add new `Capybara/VisibilityMatcher` cop. ([@aried3r])
29
+ - Fix `Capybara/CurrentPathExpectation` auto-corrector, to include option `ignore_query: true`. ([@onumis])
30
+ - Add autocorrect support for `Capybara/CurrentPathExpectation` cop. ([@ypresto])
31
+ - Fix `Capybara/FeatureMethods` not working when there is require before the spec. ([@Darhazer])
32
+ - Fix false positives in `Capybara/FeatureMethods` when feature methods are used as property names in a factory. ([@Darhazer])
33
+ - Allow configuring enabled methods in `Capybara/FeatureMethods`. ([@Darhazer])
34
+ - Fix false positive in `Capybara/FeatureMethods`. ([@Darhazer])
35
+ - Add `Capybara/CurrentPathExpectation` cop for feature specs, disallowing setting expectations on `current_path`. ([@timrogers])
36
+ - Add `RSpec/Capybara` namespace including the first cop for feature specs: `Capybara/FeatureMethods`. ([@rspeicher])
37
+
38
+ <!-- Contributors (alphabetically) -->
39
+
40
+ [@aried3r]: https://github.com/aried3r
41
+ [@bquorning]: https://github.com/bquorning
42
+ [@darhazer]: https://github.com/Darhazer
43
+ [@onumis]: https://github.com/onumis
44
+ [@pirj]: https://github.com/pirj
45
+ [@rspeicher]: https://github.com/rspeicher
46
+ [@timrogers]: https://github.com/timrogers
47
+ [@twalpole]: https://github.com/twalpole
48
+ [@ydah]: https://github.com/ydah
49
+ [@ypresto]: https://github.com/ypresto
@@ -0,0 +1,17 @@
1
+ # The RuboCop Community Code of Conduct
2
+
3
+ **Note:** We have picked the following code of conduct based on [Ruby's own
4
+ code of conduct](https://www.ruby-lang.org/en/conduct/).
5
+
6
+ This document provides a few simple community guidelines for a safe, respectful,
7
+ productive, and collaborative place for any person who is willing to contribute
8
+ to the RuboCop community. It applies to all "collaborative spaces", which are
9
+ defined as community communications channels (such as mailing lists, submitted
10
+ patches, commit comments, etc.).
11
+
12
+ - Participants will be tolerant of opposing views.
13
+ - Participants must ensure that their language and actions are free of personal
14
+ attacks and disparaging personal remarks.
15
+ - When interpreting the words and actions of others, participants should always
16
+ assume good intentions.
17
+ - Behaviour which can be reasonably considered harassment will not be tolerated.
data/MIT-LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Ian MacLeod <ian@nevir.net>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # RuboCop Capybara
2
+
3
+ [![Join the chat at https://gitter.im/rubocop-rspec/Lobby](https://badges.gitter.im/rubocop-rspec/Lobby.svg)](https://gitter.im/rubocop-rspec/Lobby)
4
+ [![Gem Version](https://badge.fury.io/rb/rubocop-capybara.svg)](https://rubygems.org/gems/rubocop-capybara)
5
+ ![CI](https://github.com/rubocop/rubocop-capybara/workflows/CI/badge.svg)
6
+
7
+ Capybara-specific analysis for your projects, as an extension to
8
+ [RuboCop](https://github.com/rubocop/rubocop).
9
+
10
+ ## Installation
11
+
12
+ Just install the `rubocop-capybara` gem
13
+
14
+ ```bash
15
+ gem install rubocop-capybara
16
+ ```
17
+
18
+ or if you use bundler put this in your `Gemfile`
19
+
20
+ ```
21
+ gem 'rubocop-capybara', require: false
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ You need to tell RuboCop to load the Capybara extension. There are three
27
+ ways to do this:
28
+
29
+ ### RuboCop configuration file
30
+
31
+ Put this into your `.rubocop.yml`.
32
+
33
+ ```yaml
34
+ require: rubocop-capybara
35
+ ```
36
+
37
+ Alternatively, use the following array notation when specifying multiple extensions.
38
+
39
+ ```yaml
40
+ require:
41
+ - rubocop-other-extension
42
+ - rubocop-capybara
43
+ ```
44
+
45
+ Now you can run `rubocop` and it will automatically load the RuboCop Capybara
46
+ cops together with the standard cops.
47
+
48
+ ### Command line
49
+
50
+ ```bash
51
+ rubocop --require rubocop-capybara
52
+ ```
53
+
54
+ ### Rake task
55
+
56
+ ```ruby
57
+ RuboCop::RakeTask.new do |task|
58
+ task.requires << 'rubocop-capybara'
59
+ end
60
+ ```
61
+
62
+ ## Documentation
63
+
64
+ You can read more about RuboCop Capybara in its [official manual](https://docs.rubocop.org/rubocop-capybara).
65
+
66
+ ## The Cops
67
+
68
+ All cops are located under
69
+ [`lib/rubocop/cop/capybara`](lib/rubocop/cop/capybara), and contain
70
+ examples/documentation.
71
+
72
+ In your `.rubocop.yml`, you may treat the Capybara cops just like any other
73
+ cop. For example:
74
+
75
+ ```yaml
76
+ Capybara/SpecificMatcher:
77
+ Exclude:
78
+ - spec/my_spec.rb
79
+ ```
80
+
81
+ ## Contributing
82
+
83
+ Checkout the [contribution guidelines](.github/CONTRIBUTING.md).
84
+
85
+ ## License
86
+
87
+ `rubocop-capybara` is MIT licensed. [See the accompanying file](MIT-LICENSE.md) for
88
+ the full text.
@@ -0,0 +1,56 @@
1
+ ---
2
+ Capybara:
3
+ Enabled: true
4
+ DocumentationBaseURL: https://docs.rubocop.org/rubocop-capybara
5
+ Include:
6
+ - "**/*_spec.rb"
7
+ - "**/spec/**/*"
8
+ - "**/test/**/*"
9
+
10
+ Capybara/CurrentPathExpectation:
11
+ Description: Checks that no expectations are set on Capybara's `current_path`.
12
+ Enabled: true
13
+ VersionAdded: '1.18'
14
+ VersionChanged: '2.0'
15
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/CurrentPathExpectation
16
+
17
+ Capybara/MatchStyle:
18
+ Description: Checks for usage of deprecated style methods.
19
+ Enabled: pending
20
+ VersionAdded: "<<next>>"
21
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/MatchStyle
22
+
23
+ Capybara/NegationMatcher:
24
+ Description: Enforces use of `have_no_*` or `not_to` for negated expectations.
25
+ Enabled: pending
26
+ VersionAdded: '2.14'
27
+ EnforcedStyle: not_to
28
+ SupportedStyles:
29
+ - have_no
30
+ - not_to
31
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/NegationMatcher
32
+
33
+ Capybara/SpecificActions:
34
+ Description: Checks for there is a more specific actions offered by Capybara.
35
+ Enabled: pending
36
+ VersionAdded: '2.14'
37
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/SpecificActions
38
+
39
+ Capybara/SpecificFinders:
40
+ Description: Checks if there is a more specific finder offered by Capybara.
41
+ Enabled: pending
42
+ VersionAdded: '2.13'
43
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/SpecificFinders
44
+
45
+ Capybara/SpecificMatcher:
46
+ Description: Checks for there is a more specific matcher offered by Capybara.
47
+ Enabled: pending
48
+ VersionAdded: '2.12'
49
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/SpecificMatcher
50
+
51
+ Capybara/VisibilityMatcher:
52
+ Description: Checks for boolean visibility in Capybara finders.
53
+ Enabled: true
54
+ VersionAdded: '1.39'
55
+ VersionChanged: '2.0'
56
+ Reference: https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/Capybara/VisibilityMatcher
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module RuboCop
6
+ module Capybara
7
+ # Builds a YAML config file from two config hashes
8
+ class ConfigFormatter
9
+ EXTENSION_ROOT_DEPARTMENT = %r{^(Capybara/)}.freeze
10
+ SUBDEPARTMENTS = [].freeze
11
+ AMENDMENTS = [].freeze
12
+ COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/'
13
+
14
+ def initialize(config, descriptions)
15
+ @config = config
16
+ @descriptions = descriptions
17
+ end
18
+
19
+ def dump
20
+ YAML.dump(unified_config)
21
+ .gsub(EXTENSION_ROOT_DEPARTMENT, "\n\\1")
22
+ .gsub(/^(\s+)- /, '\1 - ')
23
+ .gsub('"~"', '~')
24
+ # .gsub(*AMENDMENTS, "\n\\0")
25
+ end
26
+
27
+ private
28
+
29
+ def unified_config
30
+ cops.each_with_object(config.dup) do |cop, unified|
31
+ next if SUBDEPARTMENTS.include?(cop) || AMENDMENTS.include?(cop)
32
+
33
+ replace_nil(unified[cop])
34
+ unified[cop].merge!(descriptions.fetch(cop))
35
+ unified[cop]['Reference'] = reference(cop)
36
+ end
37
+ end
38
+
39
+ def cops
40
+ (descriptions.keys | config.keys).grep(EXTENSION_ROOT_DEPARTMENT)
41
+ end
42
+
43
+ def replace_nil(config)
44
+ config.each do |key, value|
45
+ config[key] = '~' if value.nil?
46
+ end
47
+ end
48
+
49
+ def reference(cop)
50
+ COP_DOC_BASE_URL + cop
51
+ end
52
+
53
+ attr_reader :config, :descriptions
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Capybara
5
+ # Extracts cop descriptions from YARD docstrings
6
+ class DescriptionExtractor
7
+ def initialize(yardocs)
8
+ @code_objects = yardocs.map(&CodeObject.public_method(:new))
9
+ end
10
+
11
+ def to_h
12
+ code_objects
13
+ .select(&:cop?)
14
+ .map(&:configuration)
15
+ .reduce(:merge)
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :code_objects
21
+
22
+ # Decorator of a YARD code object for working with documented cops
23
+ class CodeObject
24
+ RUBOCOP_COP_CLASS_NAME = 'RuboCop::Cop::Base'
25
+
26
+ def initialize(yardoc)
27
+ @yardoc = yardoc
28
+ end
29
+
30
+ # Test if the YARD code object documents a concrete cop class
31
+ #
32
+ # @return [Boolean]
33
+ def cop?
34
+ cop_subclass? && !abstract?
35
+ end
36
+
37
+ # Configuration for the documented cop that would live in default.yml
38
+ #
39
+ # @return [Hash]
40
+ def configuration
41
+ { cop_name => { 'Description' => description } }
42
+ end
43
+
44
+ private
45
+
46
+ def cop_name
47
+ Object.const_get(documented_constant).cop_name
48
+ end
49
+
50
+ def description
51
+ yardoc.docstring.split("\n\n").first.to_s
52
+ end
53
+
54
+ def documented_constant
55
+ yardoc.to_s
56
+ end
57
+
58
+ def cop_subclass?
59
+ yardoc.superclass.path == RUBOCOP_COP_CLASS_NAME
60
+ end
61
+
62
+ def abstract?
63
+ yardoc.tags.any? { |tag| tag.tag_name.eql?('abstract') }
64
+ end
65
+
66
+ attr_reader :yardoc
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Capybara
5
+ # Version information for the Capybara RuboCop plugin.
6
+ module Version
7
+ STRING = '2.17.0'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ # Checks that no expectations are set on Capybara's `current_path`.
7
+ #
8
+ # The
9
+ # https://www.rubydoc.info/github/teamcapybara/capybara/main/Capybara/RSpecMatchers#have_current_path-instance_method[`have_current_path` matcher]
10
+ # should be used on `page` to set expectations on Capybara's
11
+ # current path, since it uses
12
+ # https://github.com/teamcapybara/capybara/blob/main/README.md#asynchronous-javascript-ajax-and-friends[Capybara's waiting functionality]
13
+ # which ensures that preceding actions (like `click_link`) have
14
+ # completed.
15
+ #
16
+ # This cop does not support autocorrection in some cases.
17
+ #
18
+ # @example
19
+ # # bad
20
+ # expect(current_path).to eq('/callback')
21
+ #
22
+ # # good
23
+ # expect(page).to have_current_path('/callback')
24
+ #
25
+ # # bad (does not support autocorrection)
26
+ # expect(page.current_path).to match(variable)
27
+ #
28
+ # # good
29
+ # expect(page).to have_current_path('/callback')
30
+ #
31
+ class CurrentPathExpectation < ::RuboCop::Cop::Base
32
+ extend AutoCorrector
33
+
34
+ MSG = 'Do not set an RSpec expectation on `current_path` in ' \
35
+ 'Capybara feature specs - instead, use the ' \
36
+ '`have_current_path` matcher on `page`'
37
+
38
+ RESTRICT_ON_SEND = %i[expect].freeze
39
+
40
+ # @!method expectation_set_on_current_path(node)
41
+ def_node_matcher :expectation_set_on_current_path, <<-PATTERN
42
+ (send nil? :expect (send {(send nil? :page) nil?} :current_path))
43
+ PATTERN
44
+
45
+ # Supported matchers: eq(...) / match(/regexp/) / match('regexp')
46
+ # @!method as_is_matcher(node)
47
+ def_node_matcher :as_is_matcher, <<-PATTERN
48
+ (send
49
+ #expectation_set_on_current_path ${:to :to_not :not_to}
50
+ ${(send nil? :eq ...) (send nil? :match (regexp ...))})
51
+ PATTERN
52
+
53
+ # @!method regexp_str_matcher(node)
54
+ def_node_matcher :regexp_str_matcher, <<-PATTERN
55
+ (send
56
+ #expectation_set_on_current_path ${:to :to_not :not_to}
57
+ $(send nil? :match (str $_)))
58
+ PATTERN
59
+
60
+ def self.autocorrect_incompatible_with
61
+ [Style::TrailingCommaInArguments]
62
+ end
63
+
64
+ def on_send(node)
65
+ expectation_set_on_current_path(node) do
66
+ add_offense(node.loc.selector) do |corrector|
67
+ next unless node.chained?
68
+
69
+ autocorrect(corrector, node)
70
+ end
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def autocorrect(corrector, node)
77
+ as_is_matcher(node.parent) do |to_sym, matcher_node|
78
+ rewrite_expectation(corrector, node, to_sym, matcher_node)
79
+ end
80
+
81
+ regexp_str_matcher(node.parent) do |to_sym, matcher_node, regexp|
82
+ rewrite_expectation(corrector, node, to_sym, matcher_node)
83
+ convert_regexp_str_to_literal(corrector, matcher_node, regexp)
84
+ end
85
+ end
86
+
87
+ def rewrite_expectation(corrector, node, to_symbol, matcher_node)
88
+ current_path_node = node.first_argument
89
+ corrector.replace(current_path_node, 'page')
90
+ corrector.replace(node.parent.loc.selector, 'to')
91
+ matcher_method = if to_symbol == :to
92
+ 'have_current_path'
93
+ else
94
+ 'have_no_current_path'
95
+ end
96
+ corrector.replace(matcher_node.loc.selector, matcher_method)
97
+ add_ignore_query_options(corrector, node)
98
+ end
99
+
100
+ def convert_regexp_str_to_literal(corrector, matcher_node, regexp_str)
101
+ str_node = matcher_node.first_argument
102
+ regexp_expr = Regexp.new(regexp_str).inspect
103
+ corrector.replace(str_node, regexp_expr)
104
+ end
105
+
106
+ # `have_current_path` with no options will include the querystring
107
+ # while `page.current_path` does not.
108
+ # This ensures the option `ignore_query: true` is added
109
+ # except when the expectation is a regexp or string
110
+ def add_ignore_query_options(corrector, node)
111
+ expectation_node = node.parent.last_argument
112
+ expectation_last_child = expectation_node.children.last
113
+ return if %i[regexp str].include?(expectation_last_child.type)
114
+
115
+ corrector.insert_after(
116
+ expectation_last_child,
117
+ ', ignore_query: true'
118
+ )
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ # Checks for usage of deprecated style methods.
7
+ #
8
+ # @example when using `assert_style`
9
+ # # bad
10
+ # page.find(:css, '#first').assert_style(display: 'block')
11
+ #
12
+ # # good
13
+ # page.find(:css, '#first').assert_matches_style(display: 'block')
14
+ #
15
+ # @example when using `has_style?`
16
+ # # bad
17
+ # expect(page.find(:css, 'first')
18
+ # .has_style?(display: 'block')).to be true
19
+ #
20
+ # # good
21
+ # expect(page.find(:css, 'first')
22
+ # .matches_style?(display: 'block')).to be true
23
+ #
24
+ # @example when using `have_style`
25
+ # # bad
26
+ # expect(page).to have_style(display: 'block')
27
+ #
28
+ # # good
29
+ # expect(page).to match_style(display: 'block')
30
+ #
31
+ class MatchStyle < ::RuboCop::Cop::Base
32
+ extend AutoCorrector
33
+
34
+ MSG = 'Use `%<good>s` instead of `%<bad>s`.'
35
+ RESTRICT_ON_SEND = %i[assert_style has_style? have_style].freeze
36
+ PREFERRED_METHOD = {
37
+ 'assert_style' => 'assert_matches_style',
38
+ 'has_style?' => 'matches_style?',
39
+ 'have_style' => 'match_style'
40
+ }.freeze
41
+
42
+ def on_send(node)
43
+ method_node = node.loc.selector
44
+ add_offense(method_node) do |corrector|
45
+ corrector.replace(method_node,
46
+ PREFERRED_METHOD[method_node.source])
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def message(node)
53
+ format(MSG, good: PREFERRED_METHOD[node.source], bad: node.source)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Help methods for capybara.
6
+ module CapybaraHelp
7
+ module_function
8
+
9
+ # @param node [RuboCop::AST::SendNode]
10
+ # @param locator [String]
11
+ # @param element [String]
12
+ # @return [Boolean]
13
+ def specific_option?(node, locator, element)
14
+ attrs = CssSelector.attributes(locator).keys
15
+ return false unless replaceable_element?(node, element, attrs)
16
+
17
+ attrs.all? do |attr|
18
+ CssSelector.specific_options?(element, attr)
19
+ end
20
+ end
21
+
22
+ # @param locator [String]
23
+ # @return [Boolean]
24
+ def specific_pseudo_classes?(locator)
25
+ CssSelector.pseudo_classes(locator).all? do |pseudo_class|
26
+ replaceable_pseudo_class?(pseudo_class, locator)
27
+ end
28
+ end
29
+
30
+ # @param pseudo_class [String]
31
+ # @param locator [String]
32
+ # @return [Boolean]
33
+ def replaceable_pseudo_class?(pseudo_class, locator)
34
+ return false unless CssSelector.specific_pesudo_classes?(pseudo_class)
35
+
36
+ case pseudo_class
37
+ when 'not()' then replaceable_pseudo_class_not?(locator)
38
+ else true
39
+ end
40
+ end
41
+
42
+ # @param locator [String]
43
+ # @return [Boolean]
44
+ def replaceable_pseudo_class_not?(locator)
45
+ locator.scan(/not\(.*?\)/).all? do |negation|
46
+ CssSelector.attributes(negation).values.all? do |v|
47
+ v.is_a?(TrueClass) || v.is_a?(FalseClass)
48
+ end
49
+ end
50
+ end
51
+
52
+ # @param node [RuboCop::AST::SendNode]
53
+ # @param element [String]
54
+ # @param attrs [Array<String>]
55
+ # @return [Boolean]
56
+ def replaceable_element?(node, element, attrs)
57
+ case element
58
+ when 'link' then replaceable_to_link?(node, attrs)
59
+ else true
60
+ end
61
+ end
62
+
63
+ # @param node [RuboCop::AST::SendNode]
64
+ # @param attrs [Array<String>]
65
+ # @return [Boolean]
66
+ def replaceable_to_link?(node, attrs)
67
+ include_option?(node, :href) || attrs.include?('href')
68
+ end
69
+
70
+ # @param node [RuboCop::AST::SendNode]
71
+ # @param option [Symbol]
72
+ # @return [Boolean]
73
+ def include_option?(node, option)
74
+ node.each_descendant(:sym).find { |opt| opt.value == option }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Helps parsing css selector.
6
+ module CssSelector
7
+ COMMON_OPTIONS = %w[
8
+ above below left_of right_of near count minimum maximum between text
9
+ id class style visible obscured exact exact_text normalize_ws match
10
+ wait filter_set focused
11
+ ].freeze
12
+ SPECIFIC_OPTIONS = {
13
+ 'button' => (
14
+ COMMON_OPTIONS + %w[disabled name value title type]
15
+ ).freeze,
16
+ 'link' => (
17
+ COMMON_OPTIONS + %w[href alt title download]
18
+ ).freeze,
19
+ 'table' => (
20
+ COMMON_OPTIONS + %w[
21
+ caption with_cols cols with_rows rows
22
+ ]
23
+ ).freeze,
24
+ 'select' => (
25
+ COMMON_OPTIONS + %w[
26
+ disabled name placeholder options enabled_options
27
+ disabled_options selected with_selected multiple with_options
28
+ ]
29
+ ).freeze,
30
+ 'field' => (
31
+ COMMON_OPTIONS + %w[
32
+ checked unchecked disabled valid name placeholder
33
+ validation_message readonly with type multiple
34
+ ]
35
+ ).freeze
36
+ }.freeze
37
+ SPECIFIC_PSEUDO_CLASSES = %w[
38
+ not() disabled enabled checked unchecked
39
+ ].freeze
40
+
41
+ module_function
42
+
43
+ # @param element [String]
44
+ # @param attribute [String]
45
+ # @return [Boolean]
46
+ # @example
47
+ # specific_pesudo_classes?('button', 'name') # => true
48
+ # specific_pesudo_classes?('link', 'invalid') # => false
49
+ def specific_options?(element, attribute)
50
+ SPECIFIC_OPTIONS.fetch(element, []).include?(attribute)
51
+ end
52
+
53
+ # @param pseudo_class [String]
54
+ # @return [Boolean]
55
+ # @example
56
+ # specific_pesudo_classes?('disabled') # => true
57
+ # specific_pesudo_classes?('first-of-type') # => false
58
+ def specific_pesudo_classes?(pseudo_class)
59
+ SPECIFIC_PSEUDO_CLASSES.include?(pseudo_class)
60
+ end
61
+
62
+ # @param selector [String]
63
+ # @return [Boolean]
64
+ # @example
65
+ # id?('#some-id') # => true
66
+ # id?('.some-class') # => false
67
+ def id?(selector)
68
+ selector.start_with?('#')
69
+ end
70
+
71
+ # @param selector [String]
72
+ # @return [Boolean]
73
+ # @example
74
+ # attribute?('[attribute]') # => true
75
+ # attribute?('attribute') # => false
76
+ def attribute?(selector)
77
+ selector.start_with?('[')
78
+ end
79
+
80
+ # @param selector [String]
81
+ # @return [Array<String>]
82
+ # @example
83
+ # attributes('a[foo-bar_baz]') # => {"foo-bar_baz=>true}
84
+ # attributes('button[foo][bar]') # => {"foo"=>true, "bar"=>true}
85
+ # attributes('table[foo=bar]') # => {"foo"=>"'bar'"}
86
+ def attributes(selector)
87
+ selector.scan(/\[(.*?)\]/).flatten.to_h do |attr|
88
+ key, value = attr.split('=')
89
+ [key, normalize_value(value)]
90
+ end
91
+ end
92
+
93
+ # @param selector [String]
94
+ # @return [Boolean]
95
+ # @example
96
+ # common_attributes?('a[focused]') # => true
97
+ # common_attributes?('button[focused][visible]') # => true
98
+ # common_attributes?('table[id=some-id]') # => true
99
+ # common_attributes?('h1[invalid]') # => false
100
+ def common_attributes?(selector)
101
+ attributes(selector).keys.difference(COMMON_OPTIONS).none?
102
+ end
103
+
104
+ # @param selector [String]
105
+ # @return [Array<String>]
106
+ # @example
107
+ # pseudo_classes('button:not([disabled])') # => ['not()']
108
+ # pseudo_classes('a:enabled:not([valid])') # => ['enabled', 'not()']
109
+ def pseudo_classes(selector)
110
+ # Attributes must be excluded or else the colon in the `href`s URL
111
+ # will also be picked up as pseudo classes.
112
+ # "a:not([href='http://example.com']):enabled" => "a:not():enabled"
113
+ ignored_attribute = selector.gsub(/\[.*?\]/, '')
114
+ # "a:not():enabled" => ["not()", "enabled"]
115
+ ignored_attribute.scan(/:([^:]*)/).flatten
116
+ end
117
+
118
+ # @param selector [String]
119
+ # @return [Boolean]
120
+ # @example
121
+ # multiple_selectors?('a.cls b#id') # => true
122
+ # multiple_selectors?('a.cls') # => false
123
+ def multiple_selectors?(selector)
124
+ selector.match?(/[ >,+~]/)
125
+ end
126
+
127
+ # @param value [String]
128
+ # @return [Boolean, String]
129
+ # @example
130
+ # normalize_value('true') # => true
131
+ # normalize_value('false') # => false
132
+ # normalize_value(nil) # => false
133
+ # normalize_value("foo") # => "'foo'"
134
+ def normalize_value(value)
135
+ case value
136
+ when 'true' then true
137
+ when 'false' then false
138
+ when nil then true
139
+ else "'#{value}'"
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ # Enforces use of `have_no_*` or `not_to` for negated expectations.
7
+ #
8
+ # @example EnforcedStyle: not_to (default)
9
+ # # bad
10
+ # expect(page).to have_no_selector
11
+ # expect(page).to have_no_css('a')
12
+ #
13
+ # # good
14
+ # expect(page).not_to have_selector
15
+ # expect(page).not_to have_css('a')
16
+ #
17
+ # @example EnforcedStyle: have_no
18
+ # # bad
19
+ # expect(page).not_to have_selector
20
+ # expect(page).not_to have_css('a')
21
+ #
22
+ # # good
23
+ # expect(page).to have_no_selector
24
+ # expect(page).to have_no_css('a')
25
+ #
26
+ class NegationMatcher < ::RuboCop::Cop::Base
27
+ extend AutoCorrector
28
+ include ConfigurableEnforcedStyle
29
+
30
+ MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
31
+ CAPYBARA_MATCHERS = %w[
32
+ selector css xpath text title current_path link button
33
+ field checked_field unchecked_field select table
34
+ sibling ancestor
35
+ ].freeze
36
+ POSITIVE_MATCHERS =
37
+ Set.new(CAPYBARA_MATCHERS) { |element| :"have_#{element}" }.freeze
38
+ NEGATIVE_MATCHERS =
39
+ Set.new(CAPYBARA_MATCHERS) { |element| :"have_no_#{element}" }
40
+ .freeze
41
+ RESTRICT_ON_SEND = (POSITIVE_MATCHERS + NEGATIVE_MATCHERS).freeze
42
+
43
+ # @!method not_to?(node)
44
+ def_node_matcher :not_to?, <<~PATTERN
45
+ (send ... :not_to
46
+ (send nil? %POSITIVE_MATCHERS ...))
47
+ PATTERN
48
+
49
+ # @!method have_no?(node)
50
+ def_node_matcher :have_no?, <<~PATTERN
51
+ (send ... :to
52
+ (send nil? %NEGATIVE_MATCHERS ...))
53
+ PATTERN
54
+
55
+ def on_send(node)
56
+ return unless offense?(node.parent)
57
+
58
+ matcher = node.method_name.to_s
59
+ add_offense(offense_range(node),
60
+ message: message(matcher)) do |corrector|
61
+ corrector.replace(node.parent.loc.selector, replaced_runner)
62
+ corrector.replace(node.loc.selector,
63
+ replaced_matcher(matcher))
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def offense?(node)
70
+ (style == :have_no && not_to?(node)) ||
71
+ (style == :not_to && have_no?(node))
72
+ end
73
+
74
+ def offense_range(node)
75
+ node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
76
+ end
77
+
78
+ def message(matcher)
79
+ format(MSG,
80
+ runner: replaced_runner,
81
+ matcher: replaced_matcher(matcher))
82
+ end
83
+
84
+ def replaced_runner
85
+ case style
86
+ when :have_no
87
+ 'to'
88
+ when :not_to
89
+ 'not_to'
90
+ end
91
+ end
92
+
93
+ def replaced_matcher(matcher)
94
+ case style
95
+ when :have_no
96
+ matcher.sub('have_', 'have_no_')
97
+ when :not_to
98
+ matcher.sub('have_no_', 'have_')
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ # Checks for there is a more specific actions offered by Capybara.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # find('a').click
12
+ # find('button.cls').click
13
+ # find('a', exact_text: 'foo').click
14
+ # find('div button').click
15
+ #
16
+ # # good
17
+ # click_link
18
+ # click_button(class: 'cls')
19
+ # click_link(exact_text: 'foo')
20
+ # find('div').click_button
21
+ #
22
+ class SpecificActions < ::RuboCop::Cop::Base
23
+ MSG = "Prefer `%<good_action>s` over `find('%<selector>s').click`."
24
+ RESTRICT_ON_SEND = %i[click].freeze
25
+ SPECIFIC_ACTION = {
26
+ 'button' => 'button',
27
+ 'a' => 'link'
28
+ }.freeze
29
+
30
+ # @!method click_on_selector(node)
31
+ def_node_matcher :click_on_selector, <<-PATTERN
32
+ (send _ :find (str $_) ...)
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ click_on_selector(node.receiver) do |arg|
37
+ next unless supported_selector?(arg)
38
+ # Always check the last selector in the case of multiple selectors
39
+ # separated by whitespace.
40
+ # because the `.click` is executed on the element to
41
+ # which the last selector points.
42
+ next unless (selector = last_selector(arg))
43
+ next unless (action = specific_action(selector))
44
+ next unless CapybaraHelp.specific_option?(node.receiver, arg,
45
+ action)
46
+ next unless CapybaraHelp.specific_pseudo_classes?(arg)
47
+
48
+ range = offense_range(node, node.receiver)
49
+ add_offense(range, message: message(action, selector))
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def specific_action(selector)
56
+ SPECIFIC_ACTION[last_selector(selector)]
57
+ end
58
+
59
+ def supported_selector?(selector)
60
+ !selector.match?(/[>,+~]/)
61
+ end
62
+
63
+ def last_selector(arg)
64
+ arg.split.last[/^\w+/, 0]
65
+ end
66
+
67
+ def offense_range(node, receiver)
68
+ receiver.loc.selector.with(end_pos: node.loc.expression.end_pos)
69
+ end
70
+
71
+ def message(action, selector)
72
+ format(MSG,
73
+ good_action: good_action(action),
74
+ selector: selector)
75
+ end
76
+
77
+ def good_action(action)
78
+ "click_#{action}"
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ # Checks if there is a more specific finder offered by Capybara.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # find('#some-id')
11
+ # find('[visible][id=some-id]')
12
+ #
13
+ # # good
14
+ # find_by_id('some-id')
15
+ # find_by_id('some-id', visible: true)
16
+ #
17
+ class SpecificFinders < ::RuboCop::Cop::Base
18
+ extend AutoCorrector
19
+
20
+ include RangeHelp
21
+
22
+ MSG = 'Prefer `find_by` over `find`.'
23
+ RESTRICT_ON_SEND = %i[find].freeze
24
+
25
+ # @!method find_argument(node)
26
+ def_node_matcher :find_argument, <<~PATTERN
27
+ (send _ :find (str $_) ...)
28
+ PATTERN
29
+
30
+ def on_send(node)
31
+ find_argument(node) do |arg|
32
+ next if CssSelector.multiple_selectors?(arg)
33
+
34
+ on_attr(node, arg) if attribute?(arg)
35
+ on_id(node, arg) if CssSelector.id?(arg)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def on_attr(node, arg)
42
+ return unless (id = CssSelector.attributes(arg)['id'])
43
+
44
+ register_offense(node, replaced_arguments(arg, id))
45
+ end
46
+
47
+ def on_id(node, arg)
48
+ register_offense(node, "'#{arg.to_s.delete('#')}'")
49
+ end
50
+
51
+ def attribute?(arg)
52
+ CssSelector.attribute?(arg) &&
53
+ CssSelector.common_attributes?(arg)
54
+ end
55
+
56
+ def register_offense(node, arg_replacement)
57
+ add_offense(offense_range(node)) do |corrector|
58
+ corrector.replace(node.loc.selector, 'find_by_id')
59
+ corrector.replace(node.first_argument.loc.expression,
60
+ arg_replacement)
61
+ end
62
+ end
63
+
64
+ def replaced_arguments(arg, id)
65
+ options = to_options(CssSelector.attributes(arg))
66
+ options.empty? ? id : "#{id}, #{options}"
67
+ end
68
+
69
+ def to_options(attrs)
70
+ attrs.each.map do |key, value|
71
+ next if key == 'id'
72
+
73
+ "#{key}: #{value}"
74
+ end.compact.join(', ')
75
+ end
76
+
77
+ def offense_range(node)
78
+ range_between(node.loc.selector.begin_pos, end_pos(node))
79
+ end
80
+
81
+ def end_pos(node)
82
+ if node.loc.end
83
+ node.loc.end.end_pos
84
+ else
85
+ node.loc.expression.end_pos
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ # Checks for there is a more specific matcher offered by Capybara.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # expect(page).to have_selector('button')
12
+ # expect(page).to have_no_selector('button.cls')
13
+ # expect(page).to have_css('button')
14
+ # expect(page).to have_no_css('a.cls', href: 'http://example.com')
15
+ # expect(page).to have_css('table.cls')
16
+ # expect(page).to have_css('select')
17
+ # expect(page).to have_css('input', exact_text: 'foo')
18
+ #
19
+ # # good
20
+ # expect(page).to have_button
21
+ # expect(page).to have_no_button(class: 'cls')
22
+ # expect(page).to have_button
23
+ # expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
24
+ # expect(page).to have_table(class: 'cls')
25
+ # expect(page).to have_select
26
+ # expect(page).to have_field('foo')
27
+ #
28
+ class SpecificMatcher < ::RuboCop::Cop::Base
29
+ MSG = 'Prefer `%<good_matcher>s` over `%<bad_matcher>s`.'
30
+ RESTRICT_ON_SEND = %i[have_selector have_no_selector have_css
31
+ have_no_css].freeze
32
+ SPECIFIC_MATCHER = {
33
+ 'button' => 'button',
34
+ 'a' => 'link',
35
+ 'table' => 'table',
36
+ 'select' => 'select',
37
+ 'input' => 'field'
38
+ }.freeze
39
+
40
+ # @!method first_argument(node)
41
+ def_node_matcher :first_argument, <<-PATTERN
42
+ (send nil? _ (str $_) ... )
43
+ PATTERN
44
+
45
+ def on_send(node)
46
+ first_argument(node) do |arg|
47
+ next unless (matcher = specific_matcher(arg))
48
+ next if CssSelector.multiple_selectors?(arg)
49
+ next unless CapybaraHelp.specific_option?(node, arg, matcher)
50
+ next unless CapybaraHelp.specific_pseudo_classes?(arg)
51
+
52
+ add_offense(node, message: message(node, matcher))
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def specific_matcher(arg)
59
+ splitted_arg = arg[/^\w+/, 0]
60
+ SPECIFIC_MATCHER[splitted_arg]
61
+ end
62
+
63
+ def message(node, matcher)
64
+ format(MSG,
65
+ good_matcher: good_matcher(node, matcher),
66
+ bad_matcher: node.method_name)
67
+ end
68
+
69
+ def good_matcher(node, matcher)
70
+ node.method_name
71
+ .to_s
72
+ .gsub(/selector|css/, matcher.to_s)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Capybara
6
+ # Checks for boolean visibility in Capybara finders.
7
+ #
8
+ # Capybara lets you find elements that match a certain visibility using
9
+ # the `:visible` option. `:visible` accepts both boolean and symbols as
10
+ # values, however using booleans can have unwanted effects. `visible:
11
+ # false` does not find just invisible elements, but both visible and
12
+ # invisible elements. For expressiveness and clarity, use one of the
13
+ # symbol values, `:all`, `:hidden` or `:visible`.
14
+ # Read more in
15
+ # https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all[the documentation].
16
+ #
17
+ # @example
18
+ # # bad
19
+ # expect(page).to have_selector('.foo', visible: false)
20
+ # expect(page).to have_css('.foo', visible: true)
21
+ # expect(page).to have_link('my link', visible: false)
22
+ #
23
+ # # good
24
+ # expect(page).to have_selector('.foo', visible: :visible)
25
+ # expect(page).to have_css('.foo', visible: :all)
26
+ # expect(page).to have_link('my link', visible: :hidden)
27
+ #
28
+ class VisibilityMatcher < ::RuboCop::Cop::Base
29
+ MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
30
+ MSG_TRUE = 'Use `:visible` instead of `true`.'
31
+ CAPYBARA_MATCHER_METHODS = %w[
32
+ button
33
+ checked_field
34
+ css
35
+ field
36
+ link
37
+ select
38
+ selector
39
+ table
40
+ unchecked_field
41
+ xpath
42
+ ].flat_map do |element|
43
+ ["have_#{element}".to_sym, "have_no_#{element}".to_sym]
44
+ end
45
+
46
+ RESTRICT_ON_SEND = CAPYBARA_MATCHER_METHODS
47
+
48
+ # @!method visible_true?(node)
49
+ def_node_matcher :visible_true?, <<~PATTERN
50
+ (send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) true) ...>))
51
+ PATTERN
52
+
53
+ # @!method visible_false?(node)
54
+ def_node_matcher :visible_false?, <<~PATTERN
55
+ (send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) false) ...>))
56
+ PATTERN
57
+
58
+ def on_send(node)
59
+ visible_false?(node) { |arg| add_offense(arg, message: MSG_FALSE) }
60
+ visible_true?(node) { |arg| add_offense(arg, message: MSG_TRUE) }
61
+ end
62
+
63
+ private
64
+
65
+ def capybara_matcher?(method_name)
66
+ CAPYBARA_MATCHER_METHODS.include? method_name
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'capybara/current_path_expectation'
4
+ require_relative 'capybara/match_style'
5
+ require_relative 'capybara/negation_matcher'
6
+ require_relative 'capybara/specific_actions'
7
+ require_relative 'capybara/specific_finders'
8
+ require_relative 'capybara/specific_matcher'
9
+ require_relative 'capybara/visibility_matcher'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'yaml'
5
+
6
+ require 'rubocop'
7
+
8
+ require_relative 'rubocop/cop/capybara/mixin/capybara_help'
9
+ require_relative 'rubocop/cop/capybara/mixin/css_selector'
10
+
11
+ require_relative 'rubocop/cop/capybara_cops'
12
+
13
+ project_root = File.join(__dir__, '..')
14
+ RuboCop::ConfigLoader.inject_defaults!(project_root)
15
+ obsoletion = File.join(project_root, 'config', 'obsoletion.yml')
16
+ RuboCop::ConfigObsoletion.files << obsoletion if File.exist?(obsoletion)
17
+
18
+ RuboCop::Cop::Style::TrailingCommaInArguments.singleton_class.prepend(
19
+ Module.new do
20
+ def autocorrect_incompatible_with
21
+ super.push(RuboCop::Cop::Capybara::CurrentPathExpectation)
22
+ end
23
+ end
24
+ )
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubocop-capybara
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.17.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-12-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubocop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.41'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.41'
27
+ description: |2
28
+ Code style checking for Capybara test files (RSpec, Cucumber, Minitest).
29
+ A plugin for the RuboCop code style enforcing & linting tool.
30
+ email:
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files:
34
+ - MIT-LICENSE.md
35
+ - README.md
36
+ files:
37
+ - CHANGELOG.md
38
+ - CODE_OF_CONDUCT.md
39
+ - MIT-LICENSE.md
40
+ - README.md
41
+ - config/default.yml
42
+ - lib/rubocop-capybara.rb
43
+ - lib/rubocop/capybara/config_formatter.rb
44
+ - lib/rubocop/capybara/description_extractor.rb
45
+ - lib/rubocop/capybara/version.rb
46
+ - lib/rubocop/cop/capybara/current_path_expectation.rb
47
+ - lib/rubocop/cop/capybara/match_style.rb
48
+ - lib/rubocop/cop/capybara/mixin/capybara_help.rb
49
+ - lib/rubocop/cop/capybara/mixin/css_selector.rb
50
+ - lib/rubocop/cop/capybara/negation_matcher.rb
51
+ - lib/rubocop/cop/capybara/specific_actions.rb
52
+ - lib/rubocop/cop/capybara/specific_finders.rb
53
+ - lib/rubocop/cop/capybara/specific_matcher.rb
54
+ - lib/rubocop/cop/capybara/visibility_matcher.rb
55
+ - lib/rubocop/cop/capybara_cops.rb
56
+ homepage: https://github.com/rubocop/rubocop-capybara
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ changelog_uri: https://github.com/rubocop/rubocop-capybara/blob/main/CHANGELOG.md
61
+ documentation_uri: https://docs.rubocop.org/rubocop-capybara/
62
+ rubygems_mfa_required: 'true'
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 2.6.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.2.33
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Code style checking for Capybara test files
82
+ test_files: []