capybara_active_admin 0.3.3 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5e0ee49e0960bc3a5c3e92594431390718eaed7b46ab634422d6c7c51be233c
4
- data.tar.gz: 94ea844aa767f0319abae52517bd56fc2ca46e887e88c74c243b46e101e466c5
3
+ metadata.gz: 7734f257fe31b75bea9782d9c0cd24bc8591a0c05a9db0a0b7a6f264d8515b20
4
+ data.tar.gz: 5270545aa73089ac413bd1e1e250da449f795ffde996259b6f8d14c49476d257
5
5
  SHA512:
6
- metadata.gz: 63a541769b989dc70172f98d35833e94a9693227f382d8d2d19df2318be90b711678b0cede99ac509715bfbe95d0315fd66d261501daa47df9f21603f2e1e3ac
7
- data.tar.gz: 58fb5eee24078cfbe920c9171a36bffdeae16f25b18f4d74855242623cef63c84577ce6a89b9bd41cb76a3042f75809477891e33366e98af8ba47bfcbdc112f5
6
+ metadata.gz: 88319fec5e984e419f0dc3c130a49907f98aec9fd7f62560acdc276f10e2c670772f44198bf81e1439dc14bf3f0bf0a306b1aef22059427c14fdbdffe3a7c47d
7
+ data.tar.gz: 694336b60366c1a9ae9ca61e7df39370fd2e86eb242850c93faf0689663ebc827bd448e49f5fa238a7bb78377dc29fcdbfa4cabe2b9ee1081e4bdc039fe64b5b
@@ -0,0 +1,88 @@
1
+ name: CI
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches: [master]
6
+
7
+ permissions:
8
+ contents: read
9
+ pages: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ test:
14
+ name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / AA ${{ matrix.activeadmin }}
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ ruby: ['3.3', '3.4', '4.0']
20
+ rails: ['~> 7.2.0', '~> 8.0.0', '~> 8.1.0']
21
+ activeadmin: ['~> 3.2', '~> 3.3', '~> 3.4', '~> 3.5']
22
+ env:
23
+ RAILS_VERSION: ${{ matrix.rails }}
24
+ ACTIVE_ADMIN_VERSION: ${{ matrix.activeadmin }}
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: ${{ matrix.ruby }}
30
+ bundler-cache: true
31
+ - name: Run tests
32
+ run: bundle exec rspec spec
33
+
34
+ - name: Generate badge.json
35
+ if: matrix.ruby == '4.0' && matrix.rails == '~> 8.1.0' && matrix.activeadmin == '~> 3.5'
36
+ run: |
37
+ LAST_RUN="coverage/.last_run.json"
38
+ if [ ! -f "$LAST_RUN" ]; then
39
+ mkdir -p badge
40
+ echo '{"schemaVersion":1,"label":"coverage","message":"unknown","color":"lightgrey"}' > badge/badge.json
41
+ exit 0
42
+ fi
43
+ PERCENT=$(ruby -rjson -e "puts JSON.parse(File.read('$LAST_RUN')).dig('result','line').round(1)")
44
+ PERCENT_NUM=$(ruby -rjson -e "puts JSON.parse(File.read('$LAST_RUN')).dig('result','line')")
45
+ if ruby -e "exit(($PERCENT_NUM >= 90) ? 0 : 1)"; then COLOR="brightgreen"
46
+ elif ruby -e "exit(($PERCENT_NUM >= 75) ? 0 : 1)"; then COLOR="green"
47
+ elif ruby -e "exit(($PERCENT_NUM >= 60) ? 0 : 1)"; then COLOR="yellow"
48
+ else COLOR="red"; fi
49
+ mkdir -p badge
50
+ echo "{\"schemaVersion\":1,\"label\":\"coverage\",\"message\":\"${PERCENT}%\",\"color\":\"${COLOR}\"}" > badge/badge.json
51
+
52
+ - name: Upload badge artifact
53
+ if: matrix.ruby == '4.0' && matrix.rails == '~> 8.1.0' && matrix.activeadmin == '~> 3.5'
54
+ uses: actions/upload-artifact@v4
55
+ with:
56
+ name: coverage-badge
57
+ path: badge
58
+
59
+ deploy-coverage:
60
+ needs: test
61
+ if: github.ref == 'refs/heads/master' && github.event_name == 'push'
62
+ runs-on: ubuntu-latest
63
+ environment:
64
+ name: github-pages
65
+ url: ${{ steps.deployment.outputs.page_url }}
66
+
67
+ steps:
68
+ - uses: actions/checkout@v4
69
+ with:
70
+ ref: gh-pages
71
+
72
+ - name: Download coverage badge
73
+ uses: actions/download-artifact@v4
74
+ with:
75
+ name: coverage-badge
76
+ path: .
77
+
78
+ - name: Setup Pages
79
+ uses: actions/configure-pages@v5
80
+
81
+ - name: Upload Pages artifact
82
+ uses: actions/upload-pages-artifact@v3
83
+ with:
84
+ path: .
85
+
86
+ - name: Deploy to GitHub Pages
87
+ id: deployment
88
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,104 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL Advanced"
13
+
14
+ on:
15
+ push:
16
+ branches: [ "master" ]
17
+ pull_request:
18
+ branches: [ "master" ]
19
+ schedule:
20
+ - cron: '38 15 * * 0'
21
+
22
+ jobs:
23
+ analyze:
24
+ name: Analyze (${{ matrix.language }})
25
+ # Runner size impacts CodeQL analysis time. To learn more, please see:
26
+ # - https://gh.io/recommended-hardware-resources-for-running-codeql
27
+ # - https://gh.io/supported-runners-and-hardware-resources
28
+ # - https://gh.io/using-larger-runners (GitHub.com only)
29
+ # Consider using larger runners or machines with greater resources for possible analysis time improvements.
30
+ runs-on: 'ubuntu-latest'
31
+ permissions:
32
+ # required for all workflows
33
+ security-events: write
34
+
35
+ # required to fetch internal or private CodeQL packs
36
+ packages: read
37
+
38
+ # only required for workflows in private repositories
39
+ actions: read
40
+ contents: read
41
+
42
+ strategy:
43
+ fail-fast: false
44
+ matrix:
45
+ include:
46
+ - language: actions
47
+ build-mode: none
48
+ - language: ruby
49
+ build-mode: none
50
+ # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
51
+ # Use `c-cpp` to analyze code written in C, C++ or both
52
+ # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
53
+ # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
54
+ # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
55
+ # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
56
+ # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
57
+ # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
58
+ steps:
59
+ - name: Checkout repository
60
+ uses: actions/checkout@v4
61
+
62
+ # Add any setup steps before running the `github/codeql-action/init` action.
63
+ # This includes steps like installing compilers or runtimes (`actions/setup-node`
64
+ # or others). This is typically only required for manual builds.
65
+ # - name: Setup runtime (example)
66
+ # uses: actions/setup-example@v1
67
+
68
+ # Initializes the CodeQL tools for scanning.
69
+ - name: Initialize CodeQL
70
+ uses: github/codeql-action/init@v4
71
+ with:
72
+ languages: ${{ matrix.language }}
73
+ build-mode: ${{ matrix.build-mode }}
74
+ config: |
75
+ paths-ignore:
76
+ - '**/spec/**'
77
+ # If you wish to specify custom queries, you can do so here or in a config file.
78
+ # By default, queries listed here will override any specified in a config file.
79
+ # Prefix the list here with "+" to use these queries and those in the config file.
80
+
81
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
82
+ # queries: security-extended,security-and-quality
83
+
84
+ # If the analyze step fails for one of the languages you are analyzing with
85
+ # "We were unable to automatically build your code", modify the matrix above
86
+ # to set the build mode to "manual" for that language. Then modify this step
87
+ # to build your code.
88
+ # ℹ️ Command-line programs to run using the OS shell.
89
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
90
+ - name: Run manual build steps
91
+ if: matrix.build-mode == 'manual'
92
+ shell: bash
93
+ run: |
94
+ echo 'If you are using a "manual" build mode for one or more of the' \
95
+ 'languages you are analyzing, replace this with the commands to build' \
96
+ 'your code, for example:'
97
+ echo ' make bootstrap'
98
+ echo ' make release'
99
+ exit 1
100
+
101
+ - name: Perform CodeQL Analysis
102
+ uses: github/codeql-action/analyze@v4
103
+ with:
104
+ category: "/language:${{matrix.language}}"
data/.gitignore CHANGED
@@ -18,3 +18,4 @@ Gemfile.lock
18
18
  /docs/.vuepress/dist/
19
19
  /docs/.vuepress/public/api/
20
20
  /node_modules/
21
+ yarn.lock
data/CHANGELOG.md CHANGED
@@ -6,6 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.0.0] - 2026-04-09
10
+
11
+ ### Added
12
+ - `have_fields_date_range` matcher for date range filter fields
13
+ - `within_table_footer` finder for table footer row
14
+ - `click_table_scope` action for clicking table scope links
15
+ - `table_header_selector` selector now accepts text and options (sortable, sort_direction, column)
16
+ - `have_table_header` matcher for table header columns
17
+ - `find_table_header` finder for table header columns
18
+ - `click_table_header` action for clicking sortable table headers
19
+ - `find_action_item` finder for action item elements
20
+ - `have_action_item_link` matcher for action item links (with optional href)
21
+ - `within_action_item_dropdown` finder for action item dropdown menu
22
+ - `have_status_tag` matcher for status tag elements
23
+
24
+ ### Changed
25
+ - `within_sidebar` now scopes within the sidebar section directly using `ancestor`
26
+ - `have_table_scope` now accepts an optional title as first positional argument and `selected:` keyword arg
27
+
9
28
  ## [0.3.3] - 2020-04-17
10
29
  ### Changed
11
30
  - `batch_action_selector`, `click_batch_action` finds element by link text
data/Gemfile CHANGED
@@ -1,23 +1,16 @@
1
- # frozen_string_literal: true
2
-
3
1
  source 'https://rubygems.org'
4
-
5
- # Specify your gem's dependencies in capybara_active_admin.gemspec
6
2
  gemspec
7
3
 
8
- gem 'capybara'
9
- gem 'selenium-webdriver'
10
- # https://github.com/mattheworiordan/capybara-screenshot/issues/225#issuecomment-409407825
11
- gem 'cuprite'
12
- gem 'puma'
13
- gem 'rake', '~> 12.0'
14
- gem 'rspec-rails', '~> 4.0'
15
- gem 'rubocop', '~> 0.81.0', require: false
16
- gem 'system_test_html_screenshots', require: false
17
- gem 'yard', require: false
4
+ gem 'activeadmin', ENV.fetch('ACTIVE_ADMIN_VERSION', '~> 3.2')
5
+ gem 'rails', ENV.fetch('RAILS_VERSION', '~> 7.1.0')
6
+ gem 'sprockets-rails'
7
+ gem 'sassc-rails'
18
8
 
19
- gem 'activeadmin', ENV.fetch('ACTIVE_ADMIN_VERSION', '~> 2.0'), require: false
20
- gem 'rails', ENV.fetch('RAILS_VERSION', '6.0.0')
21
- gem 'sassc-rails', '2.1.2'
22
- gem 'sprockets', '3.7.2'
23
- gem 'sqlite3', '1.4.1'
9
+ group :test do
10
+ gem 'capybara'
11
+ gem 'cuprite'
12
+ gem 'puma'
13
+ gem 'rspec-rails', '~> 6.0'
14
+ gem 'simplecov', require: false
15
+ gem 'sqlite3', '~> 2.0'
16
+ end
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Capybara Active Admin
2
2
 
3
- [![Build Status](https://travis-ci.com/activeadmin-plugins/capybara_active_admin.svg?branch=master)](https://travis-ci.com/activeadmin-plugins/capybara_active_admin) [![Gem Version](https://badge.fury.io/rb/capybara_active_admin.svg)](https://badge.fury.io/rb/capybara_active_admin) [![Downloads](https://img.shields.io/gem/dt/capybara_active_admin)](https://rubygems.org/gems/capybara_active_admin) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
3
+ [![CI](https://github.com/activeadmin-plugins/capybara_active_admin/actions/workflows/ci.yml/badge.svg)](https://github.com/activeadmin-plugins/capybara_active_admin/actions/workflows/ci.yml)
4
+ [![Coverage](https://img.shields.io/endpoint?url=https://activeadmin-plugins.github.io/capybara_active_admin/badge.json)](https://activeadmin-plugins.github.io/capybara_active_admin/)
5
+ [![Gem Version](https://badge.fury.io/rb/capybara_active_admin.svg)](https://badge.fury.io/rb/capybara_active_admin)
6
+ [![Downloads](https://img.shields.io/gem/dt/capybara_active_admin)](https://rubygems.org/gems/capybara_active_admin)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
4
8
 
5
9
  Capybara DSL for fast and easy testing Active Admin applications.
6
10
 
@@ -24,6 +28,8 @@ Or install it yourself as:
24
28
 
25
29
  $ gem install capybara_active_admin
26
30
 
31
+ **Note: `capybara_active_admin` should be required after `capybara`.**
32
+
27
33
  ## Usage
28
34
 
29
35
  `rails_helper.rb`
data/Rakefile CHANGED
@@ -2,17 +2,7 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
- require 'rubocop/rake_task'
6
- require 'yard'
7
- require 'yard/rake/yardoc_task'
8
5
 
9
6
  RSpec::Core::RakeTask.new(:spec)
10
- RuboCop::RakeTask.new(:rubocop)
11
- YARD::Rake::YardocTask.new(:yard) do |task|
12
- task.options += [
13
- %(--output-dir=./docs/.vuepress/public/api/),
14
- %(--title=Capybara Active Admin API Reference)
15
- ]
16
- end
17
7
 
18
- task default: [:rubocop, :spec]
8
+ task default: [:spec]
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'Capybara DSL for fast and easy testing Active Admin applications.'
13
13
  spec.homepage = 'https://github.com/active_admin_plugins/capybara_active_admin'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.3.0')
16
16
 
17
17
  spec.metadata['homepage_uri'] = spec.homepage
18
18
  spec.metadata['source_code_uri'] = spec.homepage
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ['lib']
29
29
 
30
- spec.add_dependency 'activeadmin'
30
+ spec.add_dependency 'activeadmin', '>= 3.0', '< 4.0'
31
31
  # spec.add_dependency 'devise'
32
32
  spec.add_dependency 'rspec', '~> 3.0'
33
33
  end
@@ -6,7 +6,7 @@ module Capybara
6
6
  module Form
7
7
  def click_submit(value, options = {})
8
8
  selector = form_submit_selector(value)
9
- find(selector, options).click
9
+ find(selector, **options).click
10
10
  end
11
11
 
12
12
  def fill_in_file(label, options = {})
@@ -7,13 +7,13 @@ module Capybara
7
7
  module Layout
8
8
  def click_action_item(title, options = {})
9
9
  within(action_items_container_selector) do
10
- click_link(title, options)
10
+ click_link(title, **options)
11
11
  end
12
12
  end
13
13
 
14
14
  def switch_tab(tab_name, options = {})
15
15
  opts = Util.options_with_text(tab_name, options)
16
- find(tab_header_link_selector, opts).click
16
+ find(tab_header_link_selector, **opts).click
17
17
  end
18
18
 
19
19
  def click_batch_action(title, exact: true)
@@ -21,7 +21,7 @@ module Capybara
21
21
  within(dropdown_list_selector) do
22
22
  selector = batch_action_selector
23
23
  opts = Util.options_with_text(title, exact: exact)
24
- find(selector, opts).click
24
+ find(selector, **opts).click
25
25
  end
26
26
  end
27
27
 
@@ -16,6 +16,18 @@ module Capybara
16
16
  selector = %(input[id^="batch_action_item_"])
17
17
  find_all(selector, minimum: index + 1)[index].click
18
18
  end
19
+
20
+ # @param text [String] exact text of the scope to click.
21
+ def click_table_scope(text)
22
+ selector = "#{table_scopes_container_selector} > #{table_scope_selector}"
23
+ page.find(selector, exact_text: text).click
24
+ end
25
+
26
+ # @param text [String] column header text.
27
+ # @param options [Hash] options passed to find_table_header.
28
+ def click_table_header(text, options = {})
29
+ find_table_header(text, options).find('a').click
30
+ end
19
31
  end
20
32
  end
21
33
  end
@@ -22,6 +22,13 @@ module Capybara
22
22
  within(fieldset) { yield }
23
23
  end
24
24
 
25
+ # @param association_name [String]
26
+ # @yield within container have_many by passed association_name
27
+ def within_has_many(association_name)
28
+ selector = has_many_container_selector(association_name)
29
+ within(selector) { yield }
30
+ end
31
+
25
32
  # @yield within filters container.
26
33
  def within_filters
27
34
  selector = filter_form_selector
@@ -7,7 +7,7 @@ module Capybara
7
7
  module Layout
8
8
  def find_footer(options = {})
9
9
  selector = footer_selector
10
- have_selector(selector, options)
10
+ have_selector(selector, **options)
11
11
  end
12
12
 
13
13
  def within_tab_body
@@ -16,17 +16,15 @@ module Capybara
16
16
  end
17
17
 
18
18
  def within_sidebar(title, exact: nil)
19
- selector = sidebar_selector
20
-
21
- within(selector) do
22
- within_panel(title, exact: exact) { yield }
23
- end
19
+ opts = Util.options_with_text(title, exact: exact)
20
+ sidebar = page.find("#{sidebar_selector} .sidebar_section #{panel_title_selector}", **opts).ancestor('.sidebar_section')
21
+ within(sidebar) { yield }
24
22
  end
25
23
 
26
24
  def within_panel(title, exact: nil)
27
25
  title_selector = "#{panel_selector} > #{panel_title_selector}"
28
26
  title_opts = Util.options_with_text(title, exact: exact)
29
- panel_title = find(title_selector, title_opts)
27
+ panel_title = find(title_selector, **title_opts)
30
28
  panel_content = panel_title.sibling(panel_content_selector)
31
29
 
32
30
  within(panel_content) { yield }
@@ -35,6 +33,19 @@ module Capybara
35
33
  def within_modal_dialog
36
34
  within(modal_dialog_selector) { yield }
37
35
  end
36
+
37
+ # @param title [String] action item link text.
38
+ # @param exact [Boolean] whether to match the title exactly (default true).
39
+ # @return [Capybara::Node::Element] the found action item element.
40
+ def find_action_item(title, exact: true)
41
+ opts = exact ? { exact_text: title } : { text: title }
42
+ page.find(action_item_selector, **opts)
43
+ end
44
+
45
+ # @yield within action item dropdown menu list.
46
+ def within_action_item_dropdown
47
+ within("#{action_item_selector} .dropdown_menu_list_wrapper") { yield }
48
+ end
38
49
  end
39
50
  end
40
51
  end
@@ -34,6 +34,23 @@ module Capybara
34
34
  find_all(selector, minimum: index + 1)[index]
35
35
  end
36
36
 
37
+ # @yield within table>tfoot>tr
38
+ def within_table_footer
39
+ within('tfoot > tr') { yield }
40
+ end
41
+
42
+ # @param text [String] column header text.
43
+ # @param options [Hash]
44
+ # @option column [String, nil] column name override (defaults to text).
45
+ # @option sortable [Boolean] whether the column is sortable.
46
+ # @option sort_direction [String, nil] sort direction ('asc' or 'desc').
47
+ # @return [Capybara::Node::Element] the found table header element.
48
+ def find_table_header(text, options = {})
49
+ selector = table_header_selector(text, options)
50
+ opts = options.except(:column, :sortable, :sort_direction).merge(exact_text: text)
51
+ find(selector, **opts)
52
+ end
53
+
37
54
  # @yield within table>tbody>tr>td
38
55
  def within_table_cell(name)
39
56
  cell = find_table_cell(name)
@@ -8,12 +8,12 @@ module Capybara
8
8
  model = options.delete(:model)
9
9
  id = options.delete(:id)
10
10
  selector = attributes_table_selector(model: model, id: id)
11
- have_selector(selector, options)
11
+ have_selector(selector, **options)
12
12
  end
13
13
 
14
14
  def have_attribute_row(label, options = {})
15
15
  selector = attributes_row_selector(label)
16
- have_selector(selector, options)
16
+ have_selector(selector, **options)
17
17
  end
18
18
  end
19
19
  end
@@ -7,30 +7,65 @@ module Capybara
7
7
  def have_form_error(text, options = {})
8
8
  field = options.delete(:field)
9
9
  opts = Util.options_with_text(text, options)
10
- li_selector = input_container_selector field, options.slice(:exact)
10
+ li_selector = input_container_selector field, **options.slice(:exact)
11
11
 
12
- have_selector("#{li_selector} #{inline_error_selector}", opts)
12
+ have_selector("#{li_selector} #{inline_error_selector}", **opts)
13
13
  end
14
14
 
15
15
  def have_no_form_errors(options = {})
16
16
  field = options.delete(:field)
17
- li_selector = input_container_selector field, options.slice(:exact)
17
+ li_selector = input_container_selector field, **options.slice(:exact)
18
18
 
19
- have_none_of_selectors(:css, "#{li_selector} #{inline_error_selector}", options)
19
+ have_none_of_selectors(:css, "#{li_selector} #{inline_error_selector}", **options)
20
20
  end
21
21
 
22
22
  def have_semantic_error(text, options = {})
23
23
  opts = Util.options_with_text(text, options)
24
- have_selector(semantic_error_selector, opts)
24
+ have_selector(semantic_error_selector, **opts)
25
25
  end
26
26
 
27
27
  def have_semantic_errors(options = {})
28
- have_selector(semantic_error_selector, options)
28
+ have_selector(semantic_error_selector, **options)
29
29
  end
30
30
 
31
31
  def have_has_many_fields_for(association_name, options = {})
32
32
  selector = has_many_fields_selector(association_name)
33
- have_selector(selector, options)
33
+ have_selector(selector, **options)
34
+ end
35
+
36
+ # @param label [String] label text of the date range filter.
37
+ # @param options [Hash]
38
+ # @option from [String, nil] expected value of the "from" field.
39
+ # @option to [String, nil] expected value of the "to" field.
40
+ # @option exact [Boolean, nil] whether to match field values exactly.
41
+ # @example
42
+ # expect(page).to have_fields_date_range('Created At', from: '2020-01-01', to: '2020-12-31')
43
+ #
44
+ def have_fields_date_range(label, options = {})
45
+ exact = options[:exact]
46
+ satisfy do |actual|
47
+ expect(actual).to have_selector('div.filter_date_range label', text: label)
48
+ container = actual.find('div.filter_date_range label', text: label).ancestor('div.filter_date_range')
49
+ base_name = container[:id].gsub(/_input\z/, '')
50
+ expect(container).to have_field("#{base_name}_gteq_datetime", with: options[:from].to_s, exact: exact)
51
+ expect(container).to have_field("#{base_name}_lteq_datetime", with: options[:to].to_s, exact: exact)
52
+ end
53
+ end
54
+
55
+ # @param text [String] button title
56
+ # @param options [Hash]
57
+ # @option selector [String, nil] optional selector to append
58
+ # @option disabled [Boolean] button disabled or not (default false)
59
+ # @example
60
+ # expect(page).to have_submit_input('Submit') # check that submit input is present
61
+ # expect(page).to have_submit_input('Submit', selector: '.custom-class') # check custom class presence
62
+ # expect(page).to have_submit_input('Submit', disabled: true) # check that submit input is disabled
63
+ # expect(page).to have_submit_input('Submit', disabled: true, count: 0) # check that submit input is enabled
64
+ #
65
+ def have_submit_input(text, options = {})
66
+ selector = "#{form_submit_selector(text)}#{options.delete(:selector)}"
67
+ selector += '[disabled="disabled"].disabled' if options.delete(:disabled)
68
+ have_selector(selector, **options)
34
69
  end
35
70
  end
36
71
  end
@@ -7,42 +7,68 @@ module Capybara
7
7
  module Layout
8
8
  def have_action_item(text, options = {})
9
9
  opts = Util.options_with_text(text, options)
10
- have_selector(action_item_selector, opts)
10
+ have_selector(action_item_selector, **opts)
11
11
  end
12
12
 
13
13
  def have_page_title(text, options = {})
14
14
  opts = Util.options_with_text(text, options)
15
- have_selector(page_title_selector, opts)
15
+ have_selector(page_title_selector, **opts)
16
16
  end
17
17
 
18
18
  def have_flash_message(text, options = {})
19
19
  type = options.delete(:type)
20
20
  opts = Util.options_with_text(text, options)
21
21
  selector = flash_message_selector(type)
22
- have_selector(selector, opts)
22
+ have_selector(selector, **opts)
23
23
  end
24
24
 
25
25
  def have_footer(options = {})
26
26
  selector = footer_selector
27
- have_selector(selector, options)
27
+ have_selector(selector, **options)
28
28
  end
29
29
 
30
30
  def have_panel(title, options = {})
31
31
  title_selector = "#{panel_selector} > #{panel_title_selector}"
32
32
  opts = Util.options_with_text(title, options)
33
- have_selector(title_selector, opts)
33
+ have_selector(title_selector, **opts)
34
34
  end
35
35
 
36
36
  def have_sidebar(title, options = {})
37
37
  title_selector = "#{sidebar_selector} #{panel_selector} > #{panel_title_selector}"
38
38
  opts = Util.options_with_text(title, options)
39
- have_selector(title_selector, opts)
39
+ have_selector(title_selector, **opts)
40
40
  end
41
41
 
42
42
  def have_batch_action(title, exact: true)
43
43
  selector = "#{dropdown_list_selector} #{batch_action_selector}"
44
44
  opts = Util.options_with_text(title, exact: exact)
45
- have_selector(selector, opts)
45
+ have_selector(selector, **opts)
46
+ end
47
+
48
+ # @param title [String] action item link text.
49
+ # @param exact [Boolean] whether to match the title exactly (default true).
50
+ # @param href [String, nil] expected href attribute of the link.
51
+ # @param options [Hash] additional options passed to have_selector.
52
+ # @example
53
+ # expect(page).to have_action_item_link('New User')
54
+ # expect(page).to have_action_item_link('New User', href: new_admin_user_path)
55
+ #
56
+ def have_action_item_link(title, exact: true, href: nil, **options)
57
+ opts = exact ? { exact_text: title } : { text: title }
58
+ opts.merge!(options)
59
+ selector = "#{action_item_selector} > a"
60
+ selector += "[href=\"#{href}\"]" if href.present?
61
+ have_selector(selector, **opts)
62
+ end
63
+
64
+ # @param type [String, Symbol] status tag type (e.g. :yes, :no, :warning).
65
+ # @param options [Hash] additional options passed to have_selector (e.g. exact_text:).
66
+ # @example
67
+ # expect(page).to have_status_tag(:yes)
68
+ # expect(page).to have_status_tag(:error, exact_text: 'DOWN')
69
+ #
70
+ def have_status_tag(type, **options)
71
+ have_selector("span.status_tag.#{type}", **options)
46
72
  end
47
73
  end
48
74
  end