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 +4 -4
- data/CHANGELOG.md +73 -0
- data/README.md +74 -7
- data/lib/capybara/active_admin/actions/form.rb +1 -1
- data/lib/capybara/active_admin/actions/layout.rb +3 -3
- data/lib/capybara/active_admin/actions/table.rb +12 -0
- data/lib/capybara/active_admin/finders/form.rb +7 -0
- data/lib/capybara/active_admin/finders/layout.rb +18 -7
- data/lib/capybara/active_admin/finders/table.rb +17 -0
- data/lib/capybara/active_admin/matchers/attributes_table.rb +2 -2
- data/lib/capybara/active_admin/matchers/form.rb +42 -7
- data/lib/capybara/active_admin/matchers/layout.rb +33 -7
- data/lib/capybara/active_admin/matchers/table.rb +39 -3
- data/lib/capybara/active_admin/selectors/attributes_table.rb +3 -1
- data/lib/capybara/active_admin/selectors/form.rb +7 -1
- data/lib/capybara/active_admin/selectors/table.rb +30 -5
- data/lib/capybara/active_admin/util.rb +41 -4
- data/lib/capybara/active_admin/version.rb +1 -1
- metadata +12 -26
- data/.gitignore +0 -20
- data/.rspec +0 -3
- data/.rubocop.yml +0 -82
- data/.travis.yml +0 -45
- data/CODE_OF_CONDUCT.md +0 -74
- data/Gemfile +0 -23
- data/Rakefile +0 -18
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/capybara_active_admin.gemspec +0 -33
- data/docs/.vuepress/config.js +0 -12
- data/docs/.vuepress/public/.keep +0 -0
- data/docs/.vuepress/styles/index.styl +0 -3
- data/docs/README.md +0 -41
- data/docs/guide/README.md +0 -113
- data/package.json +0 -9
- data/yarn.lock +0 -7947
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bafb813d42831673f3ea1a80e97e410b1dced0c55cdf4c043fbc24ff5fbcc11e
|
|
4
|
+
data.tar.gz: 6115f349a6204615861dd23f0b022c66086e72f9a054bc12a808858ac7824d38
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
[](https://github.com/activeadmin-plugins/capybara_active_admin/actions/workflows/ci.yml)
|
|
4
|
+
[](https://activeadmin-plugins.github.io/capybara_active_admin/)
|
|
5
|
+
[](https://badge.fury.io/rb/capybara_active_admin)
|
|
6
|
+
[](https://rubygems.org/gems/capybara_active_admin)
|
|
7
|
+
[](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'
|
|
56
|
-
expect(page).to have_table_cell('John Doe',
|
|
57
|
-
expect(page).to_not have_table_cell('John Doe',
|
|
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',
|
|
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.
|
|
@@ -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
|
-
|
|
20
|
-
|
|
21
|
-
within(
|
|
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 =
|
|
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
|