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 +7 -0
 - data/CHANGELOG.md +49 -0
 - data/CODE_OF_CONDUCT.md +17 -0
 - data/MIT-LICENSE.md +21 -0
 - data/README.md +88 -0
 - data/config/default.yml +56 -0
 - data/lib/rubocop/capybara/config_formatter.rb +56 -0
 - data/lib/rubocop/capybara/description_extractor.rb +70 -0
 - data/lib/rubocop/capybara/version.rb +10 -0
 - data/lib/rubocop/cop/capybara/current_path_expectation.rb +123 -0
 - data/lib/rubocop/cop/capybara/match_style.rb +58 -0
 - data/lib/rubocop/cop/capybara/mixin/capybara_help.rb +78 -0
 - data/lib/rubocop/cop/capybara/mixin/css_selector.rb +144 -0
 - data/lib/rubocop/cop/capybara/negation_matcher.rb +104 -0
 - data/lib/rubocop/cop/capybara/specific_actions.rb +83 -0
 - data/lib/rubocop/cop/capybara/specific_finders.rb +91 -0
 - data/lib/rubocop/cop/capybara/specific_matcher.rb +77 -0
 - data/lib/rubocop/cop/capybara/visibility_matcher.rb +71 -0
 - data/lib/rubocop/cop/capybara_cops.rb +9 -0
 - data/lib/rubocop-capybara.rb +24 -0
 - metadata +82 -0
 
    
        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
         
     | 
    
        data/CODE_OF_CONDUCT.md
    ADDED
    
    | 
         @@ -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 
     | 
    
         
            +
            [](https://gitter.im/rubocop-rspec/Lobby)
         
     | 
| 
      
 4 
     | 
    
         
            +
            [](https://rubygems.org/gems/rubocop-capybara)
         
     | 
| 
      
 5 
     | 
    
         
            +
            
         
     | 
| 
      
 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.
         
     | 
    
        data/config/default.yml
    ADDED
    
    | 
         @@ -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,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: []
         
     |