capybara_active_admin 0.3.3 → 2.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: bafb813d42831673f3ea1a80e97e410b1dced0c55cdf4c043fbc24ff5fbcc11e
4
+ data.tar.gz: 6115f349a6204615861dd23f0b022c66086e72f9a054bc12a808858ac7824d38
5
5
  SHA512:
6
- metadata.gz: 63a541769b989dc70172f98d35833e94a9693227f382d8d2d19df2318be90b711678b0cede99ac509715bfbe95d0315fd66d261501daa47df9f21603f2e1e3ac
7
- data.tar.gz: 58fb5eee24078cfbe920c9171a36bffdeae16f25b18f4d74855242623cef63c84577ce6a89b9bd41cb76a3042f75809477891e33366e98af8ba47bfcbdc112f5
6
+ metadata.gz: c2f006051d76bf7c4adc0d23e0098ff5ecf9703be0e11bf43d08301c06bd3770b23c85661218940c1ef60af8cad8a1306802436f6e85469ac2a7f436276559ae
7
+ data.tar.gz: 49557743c791ecfb6069a755771e47946ec0985627ee8c2b351bada208a38cd062ad65dff8c70f75d070f043c2cde102ff924d2ef5d5c680f20a85dad7513357
data/CHANGELOG.md CHANGED
@@ -6,6 +6,79 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.0.0] - 2026-10-01
10
+
11
+ Breaking: the selectors below build a different string than 1.0.0 did for the
12
+ same input, so a suite that passed on 1.0.0 can fail on this version without
13
+ changing a line.
14
+
15
+ ### Migrating from 1.0.0
16
+
17
+ - If you wrote a helper that normalized labels before handing them to
18
+ `table_cell_selector` / `table_header_selector` / `attributes_row_selector`,
19
+ delete it. The gem now makes the same `parameterize(separator: '_')` call
20
+ ActiveAdmin makes, so normalizing twice corrupts the result — the second
21
+ pass strips the `_` the first one inserted.
22
+ - Labels with no Latin-transliterable characters (`'Имя'`, `'名前'`, `'№'`)
23
+ now raise instead of silently matching every such row. Match on text, or
24
+ pass the attribute name.
25
+ - Rails 7.2 is no longer tested.
26
+
27
+ ### Fixed
28
+ - `attributes_table_selector`, `form_selector` and `table_selector` now ask the
29
+ model for its DOM name (`model_name.singular` / `.plural`, the same source
30
+ Arbre and ActiveAdmin use) instead of `gsub(' ', '_')`. A namespaced name
31
+ previously kept its `::` and produced `div.attributes_table.billing::employee`
32
+ — not a selector, so it aborted the example instead of failing it.
33
+ `table_selector` also accepts a model class now.
34
+
35
+ ### Changed
36
+ - `table_cell_selector`, `table_header_selector` and `attributes_row_selector`
37
+ now derive the CSS class with `parameterize(separator: '_')`, the same call
38
+ ActiveAdmin makes in `AttributesTable#row` and `TableFor::Column#html_class`,
39
+ instead of three different hand-rolled substitutions. Labels containing
40
+ punctuation change selector: `'E-mail'` was `col-email` and is now
41
+ `col-e-mail`, `"Customer's Name"` was `col-customers_name` and is now
42
+ `col-customer_s_name`, `:full_name` was `col-fullname` and is now
43
+ `col-full_name`. Previously these matched nothing (or raised
44
+ `Nokogiri::CSS::SyntaxError`), so an app carrying a workaround that
45
+ pre-normalized labels before calling these helpers should drop it.
46
+ - `table_header_selector(nil, column: 'x')` now honours the override instead
47
+ of returning the generic header selector.
48
+ - tested against Ruby 3.3, 3.4, 4.0 and Rails 8.0, 8.1 (EOL Rails 7.2 dropped from CI)
49
+
50
+ ### Fixed
51
+ - a label that `parameterize` cannot transliterate (`'Имя'`, `'名前'`, `'№'`, or
52
+ a blank string) now raises `ArgumentError` instead of building a bare
53
+ `.row-` / `.col-` selector. ActiveAdmin renders the same empty class for
54
+ every such label, so that selector matched all of them at once and the
55
+ matcher could pass against the wrong row.
56
+
57
+ ## [1.0.0] - 2026-04-09
58
+
59
+ ### Added
60
+ - `have_fields_date_range` matcher for date range filter fields
61
+ - `within_table_footer` finder for table footer row
62
+ - `click_table_scope` action for clicking table scope links
63
+ - `table_header_selector` selector now accepts text and options (sortable, sort_direction, column)
64
+ - `have_table_header` matcher for table header columns
65
+ - `find_table_header` finder for table header columns
66
+ - `click_table_header` action for clicking sortable table headers
67
+ - `find_action_item` finder for action item elements
68
+ - `have_action_item_link` matcher for action item links (with optional href)
69
+ - `within_action_item_dropdown` finder for action item dropdown menu
70
+ - `have_status_tag` matcher for status tag elements
71
+
72
+ ### Changed
73
+ - `have_table_scope` now accepts an optional title as first positional argument and `selected:` keyword arg.
74
+ **Breaking**, and not spelled out at the time: the `active:` key it replaced
75
+ is no longer consumed, so it falls through to `have_selector` and Capybara
76
+ rejects it with `ArgumentError: Invalid option(s) :active`. Rename
77
+ `active:` to `selected:`; the selector built is the same.
78
+ - `within_sidebar` now scopes within the sidebar section directly using `ancestor`.
79
+ **Breaking** for a block that relied on the old scope being the panel rather
80
+ than the whole `.sidebar_section`.
81
+
9
82
  ## [0.3.3] - 2020-04-17
10
83
  ### Changed
11
84
  - `batch_action_selector`, `click_batch_action` finds element by link text
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`
@@ -49,17 +55,17 @@ RSpec.describe 'Users', js: true do
49
55
 
50
56
  within_table_for('users') do
51
57
  expect(page).to have_table_row(count: 2)
52
- expect(page).to have_table_cell('John Doe')
58
+ expect(page).to have_table_cell(text: 'John Doe')
53
59
 
54
60
  within_table_row(id: john.id) do
55
- expect(page).to have_table_cell('John Doe', row_id: john.id)
56
- expect(page).to have_table_cell('John Doe', row_id: john.id, col_name: 'Full Name')
57
- expect(page).to_not have_table_cell('John Doe', row_id: john.id, col_name: 'Id')
61
+ expect(page).to have_table_cell(text: 'John Doe')
62
+ expect(page).to have_table_cell(text: 'John Doe', column: 'Full Name')
63
+ expect(page).to_not have_table_cell(text: 'John Doe', column: 'Id')
58
64
  end
59
65
 
60
66
  within_table_row(id: jane.id) do
61
- expect(page).to_not have_table_cell('John Doe')
62
- expect(page).to_not have_table_cell('John Doe', col_name: 'Full Name')
67
+ expect(page).to_not have_table_cell(text: 'John Doe')
68
+ expect(page).to_not have_table_cell(text: 'John Doe', column: 'Full Name')
63
69
  end
64
70
  end
65
71
  end
@@ -88,6 +94,67 @@ end
88
94
  See `spec/support` for more user examples.
89
95
  See `capybara/active_admin/test_helpers.rb` for available DSL methods.
90
96
 
97
+ ## How labels become selectors
98
+
99
+ Most helpers take the label you see on the page and build a CSS selector from
100
+ it, because that is how ActiveAdmin names its own elements. Two derivations are
101
+ worth knowing, since a mismatch shows up as "element not found" with no hint of
102
+ why.
103
+
104
+ ### Column and row labels
105
+
106
+ ActiveAdmin builds the class with `parameterize(separator: '_')` —
107
+ `AttributesTable#row` and `TableFor::Column#html_class` — and so does this gem:
108
+
109
+ | label | class ActiveAdmin renders |
110
+ | --- | --- |
111
+ | `'Full Name'` | `col-full_name` |
112
+ | `:full_name` | `col-full_name` |
113
+ | `'VAT / TAX Number'` | `col-vat_tax_number` |
114
+ | `'E-mail'` | `col-e-mail` |
115
+ | `"Customer's Name"` | `col-customer_s_name` |
116
+ | `'# of DIDs'` | `col-of_dids` |
117
+
118
+ So pass the label as it appears, and do **not** normalize it yourself first —
119
+ doing it twice strips the separator the first pass inserted.
120
+
121
+ Two cases have no class to match:
122
+
123
+ ```ruby
124
+ # ActiveAdmin renders class="row" with no row-* at all when you give your own
125
+ row :salary, class: 'money'
126
+
127
+ # parameterize drops everything non-Latin, so AA renders a bare class="row row-"
128
+ # for every such label and they cannot be told apart. The gem raises rather
129
+ # than match the wrong one.
130
+ row 'Имя'
131
+ ```
132
+
133
+ Match on text in both cases, or pass the attribute name rather than the label.
134
+
135
+ ### Model and resource names
136
+
137
+ `have_attributes_table(model:)` and `within_form_for` follow the **model
138
+ class** — Arbre uses `model_name.singular`:
139
+
140
+ ```ruby
141
+ have_attributes_table(model: Billing::Employee) # div.attributes_table.billing_employee
142
+ have_attributes_table(model: 'Billing::Employee') # same
143
+ ```
144
+
145
+ `have_table(resource_name:)` and `within_table_for` follow the name the
146
+ resource was **registered** under, which `as:` detaches from the model:
147
+
148
+ ```ruby
149
+ ActiveAdmin.register Billing::Employee, as: 'Business Employee'
150
+
151
+ within_table_for('Business Employee') { ... } # table#index_table_business_employees
152
+ within_table_for(Billing::Employee) { ... } # table#index_table_billing_employees -- wrong
153
+ ```
154
+
155
+ A renamed resource has to be addressed by its registered name; the class cannot
156
+ know it.
157
+
91
158
  ## Development
92
159
 
93
160
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -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
@@ -14,7 +14,7 @@ module Capybara
14
14
  def have_table(options = {})
15
15
  resource_name = options.delete(:resource_name)
16
16
  selector = table_selector(resource_name)
17
- have_selector(selector, options)
17
+ have_selector(selector, **options)
18
18
  end
19
19
 
20
20
  # @param options [Hash]
@@ -30,7 +30,7 @@ module Capybara
30
30
  def have_table_row(options = {})
31
31
  row_id = options.delete(:id)
32
32
  selector = table_row_selector(row_id)
33
- have_selector(selector, options)
33
+ have_selector(selector, **options)
34
34
  end
35
35
 
36
36
  # @param options [Hash]
@@ -51,7 +51,43 @@ module Capybara
51
51
  column = options.delete(:column)
52
52
  selector = table_cell_selector(column)
53
53
 
54
- have_selector(selector, options)
54
+ have_selector(selector, **options)
55
+ end
56
+
57
+ # @param options [Hash]
58
+ # @option count [Integer] qty of nodes
59
+ def have_table_scopes(options = {})
60
+ have_selector("#{table_scopes_container_selector} > #{table_scope_selector}", **options)
61
+ end
62
+
63
+ # @param title [String, nil] exact title of scope to match.
64
+ # @param selected [Boolean] whether to match selected (active) scope only (default false).
65
+ # @param options [Hash] additional options passed to have_selector.
66
+ def have_table_scope(title = nil, selected: false, **options)
67
+ selector = "#{table_scopes_container_selector} > #{table_scope_selector}"
68
+ if title.nil?
69
+ selector = selected ? "#{selector}.selected" : "#{selector}:not(.selected)"
70
+ have_selector(selector, **options)
71
+ else
72
+ selector = selected ? "#{selector}.selected" : selector
73
+ have_selector(selector, exact_text: title.to_s, **options)
74
+ end
75
+ end
76
+
77
+ # @param text [String] column header text.
78
+ # @param options [Hash]
79
+ # @option column [String, nil] column name override (defaults to text).
80
+ # @option sortable [Boolean] whether the column is sortable.
81
+ # @option sort_direction [String, nil] sort direction ('asc' or 'desc').
82
+ # @example
83
+ # expect(page).to have_table_header('Full Name')
84
+ # expect(page).to have_table_header('Full Name', sortable: true)
85
+ # expect(page).to have_table_header('Full Name', sort_direction: :asc)
86
+ #
87
+ def have_table_header(text, options = {})
88
+ selector = table_header_selector(text, options)
89
+ opts = options.except(:column, :sortable, :sort_direction).merge(exact_text: text)
90
+ have_selector(selector, **opts)
55
91
  end
56
92
  end
57
93
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'capybara/active_admin/util'
4
+
3
5
  module Capybara
4
6
  module ActiveAdmin
5
7
  module Selectors
@@ -18,7 +20,7 @@ module Capybara
18
20
  def attributes_row_selector(label = nil)
19
21
  return 'tr.row > td' if label.nil?
20
22
 
21
- label = label.to_s.gsub(' ', '_').downcase
23
+ label = Util.css_class_for_label(label)
22
24
  "tr.row.row-#{label} > td"
23
25
  end
24
26
  end
@@ -35,6 +35,12 @@ module Capybara
35
35
  "div.has_many_container.#{association_name} > fieldset.inputs.has_many_fields"
36
36
  end
37
37
 
38
+ # @param association_name [String]
39
+ # @return [String] .has_many_container selector.
40
+ def has_many_container_selector(association_name)
41
+ ".has_many_container.#{association_name}"
42
+ end
43
+
38
44
  # @param text [String, nil] submit button text.
39
45
  # @return [String] selector.
40
46
  def form_submit_selector(text = nil)
@@ -50,7 +56,7 @@ module Capybara
50
56
  return 'li' if label.nil?
51
57
 
52
58
  label_opts = Util.options_with_text(label, exact: exact)
53
- label_node = find(label_selector, label_opts)
59
+ label_node = find(label_selector, **label_opts)
54
60
  li_id = label_node.ancestor('li')[:id]
55
61
  "li##{li_id}"
56
62
  end