capybara 3.11.1 → 3.12.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/History.md +18 -1
- data/README.md +4 -4
- data/lib/capybara.rb +7 -0
- data/lib/capybara/node/element.rb +7 -1
- data/lib/capybara/node/matchers.rb +0 -18
- data/lib/capybara/queries/base_query.rb +1 -1
- data/lib/capybara/queries/selector_query.rb +24 -39
- data/lib/capybara/result.rb +4 -4
- data/lib/capybara/selector.rb +7 -15
- data/lib/capybara/selector/builders/css_builder.rb +59 -27
- data/lib/capybara/selector/builders/xpath_builder.rb +50 -35
- data/lib/capybara/selector/css.rb +7 -7
- data/lib/capybara/selector/filter.rb +1 -0
- data/lib/capybara/selector/filter_set.rb +17 -15
- data/lib/capybara/selector/filters/locator_filter.rb +19 -0
- data/lib/capybara/selector/regexp_disassembler.rb +104 -61
- data/lib/capybara/selector/selector.rb +14 -5
- data/lib/capybara/selenium/driver.rb +14 -9
- data/lib/capybara/selenium/driver_specializations/{marionette_driver.rb → firefox_driver.rb} +3 -3
- data/lib/capybara/selenium/nodes/{marionette_node.rb → firefox_node.rb} +1 -1
- data/lib/capybara/spec/session/all_spec.rb +8 -1
- data/lib/capybara/spec/session/assert_selector_spec.rb +0 -10
- data/lib/capybara/spec/session/click_button_spec.rb +5 -3
- data/lib/capybara/spec/session/click_link_spec.rb +5 -5
- data/lib/capybara/spec/session/find_spec.rb +1 -1
- data/lib/capybara/spec/session/first_spec.rb +1 -1
- data/lib/capybara/spec/session/has_css_spec.rb +7 -0
- data/lib/capybara/spec/session/has_xpath_spec.rb +17 -0
- data/lib/capybara/spec/session/node_spec.rb +10 -3
- data/lib/capybara/spec/session/window/window_spec.rb +2 -2
- data/lib/capybara/spec/spec_helper.rb +1 -2
- data/lib/capybara/spec/views/obscured.erb +3 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/css_builder_spec.rb +99 -0
- data/spec/result_spec.rb +6 -0
- data/spec/selector_spec.rb +26 -1
- data/spec/selenium_spec_chrome.rb +18 -16
- data/spec/selenium_spec_chrome_remote.rb +0 -2
- data/spec/{selenium_spec_marionette.rb → selenium_spec_firefox.rb} +31 -25
- data/spec/selenium_spec_firefox_remote.rb +4 -6
- data/spec/shared_selenium_session.rb +2 -2
- data/spec/spec_helper.rb +5 -5
- data/spec/xpath_builder_spec.rb +91 -0
- metadata +10 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b3b4d3a612269d9ee165e7ed94e1e62f91c6fd85bcc5c5ae614c25d0c22bd32
|
4
|
+
data.tar.gz: a2bdfa0c2398789bd15d791e3b2d0d03d9c3371033e4f4e4d21e302db845c93f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4815632f5bc14c932e9b5181d68584ac3088f11241e8ee3c19f3ee0a27190f8ad1544b88a532f901eaf79f9ae3c2f721dfca1d2d6d20d098a40438042b9f64a
|
7
|
+
data.tar.gz: eb2765462cdbdda76a4ca5d100b93688029d1ca727c00579c5e9e5d77f26fc4a0fe64bbe3616bb3f89b34e730c337e242b319b2ea606b5287a780f8219ee5b08
|
data/History.md
CHANGED
@@ -1,7 +1,24 @@
|
|
1
|
+
# Version 3.12.0
|
2
|
+
Release date: 2018-11-28
|
3
|
+
|
4
|
+
### Added
|
5
|
+
|
6
|
+
* Support Ruby 2.6 endless range in Result#[] and query `:between` option
|
7
|
+
* Pre-registered headless firefox driver :selenium_headless [Andrew Havens]
|
8
|
+
* Selenium driver now defaults to clearing `sessionStorage` and `localStorage`. To disable pass `clear_local_storage: false` and/or `clear_session_storage: false` when creating Capybara::Selenium::Driver instance in your driver registration
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
* Raise error if only :x or :y are passed as an offset to click methods
|
13
|
+
|
14
|
+
### Removed
|
15
|
+
|
16
|
+
* Support for RSpec < 3.5
|
17
|
+
|
1
18
|
# Version 3.11.1
|
2
19
|
Release date: 2018-11-16
|
3
20
|
|
4
|
-
###Fixed
|
21
|
+
### Fixed
|
5
22
|
|
6
23
|
* Fixed :link_or_button XPath generation when it has had an expression filter added
|
7
24
|
|
data/README.md
CHANGED
@@ -6,8 +6,7 @@
|
|
6
6
|
[](https://gitter.im/jnicklas/capybara?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
7
7
|
[](https://dependabot.com/compatibility-score.html?dependency-name=capybara&package-manager=bundler&version-scheme=semver)
|
8
8
|
|
9
|
-
**Note** You are viewing the README for the 3.
|
10
|
-
|
9
|
+
**Note** You are viewing the README for the 3.12.x version of Capybara.
|
11
10
|
|
12
11
|
Capybara helps you test web applications by simulating how a real user would
|
13
12
|
interact with your app. It is agnostic about the driver running your tests and
|
@@ -139,7 +138,7 @@ There are also explicit tags for each registered driver set up for you (`@seleni
|
|
139
138
|
|
140
139
|
## <a name="using-capybara-with-rspec"></a>Using Capybara with RSpec
|
141
140
|
|
142
|
-
Load RSpec 3.
|
141
|
+
Load RSpec 3.5+ support by adding the following line (typically to your
|
143
142
|
`spec_helper.rb` file):
|
144
143
|
|
145
144
|
```ruby
|
@@ -394,6 +393,7 @@ and add it to your Gemfile if you're using bundler.
|
|
394
393
|
Capybara pre-registers a number of named drives that use Selenium - they are:
|
395
394
|
|
396
395
|
* :selenium => Selenium driving Firefox
|
396
|
+
* :selenium_headless => Selenium driving Firefox in a headless configuration
|
397
397
|
* :selenium_chrome => Selenium driving Chrome
|
398
398
|
* :selenium_chrome_headless => Selenium driving Chrome in a headless configuration
|
399
399
|
|
@@ -404,7 +404,7 @@ to the browsers. See the section on adding and configuring drivers.
|
|
404
404
|
|
405
405
|
**Note**: drivers which run the server in a different thread may not share the
|
406
406
|
same transaction as your tests, causing data not to be shared between your test
|
407
|
-
and test server, see
|
407
|
+
and test server, see [Transactions and database setup](#transactions-and-database-setup) below.
|
408
408
|
|
409
409
|
### <a name="capybara-webkit"></a>Capybara-webkit
|
410
410
|
|
data/lib/capybara.rb
CHANGED
@@ -532,6 +532,13 @@ Capybara.register_driver :selenium do |app|
|
|
532
532
|
Capybara::Selenium::Driver.new(app)
|
533
533
|
end
|
534
534
|
|
535
|
+
Capybara.register_driver :selenium_headless do |app|
|
536
|
+
Capybara::Selenium::Driver.load_selenium
|
537
|
+
browser_options = ::Selenium::WebDriver::Firefox::Options.new
|
538
|
+
browser_options.args << '-headless'
|
539
|
+
Capybara::Selenium::Driver.new(app, browser: :firefox, options: browser_options)
|
540
|
+
end
|
541
|
+
|
535
542
|
Capybara.register_driver :selenium_chrome do |app|
|
536
543
|
Capybara::Selenium::Driver.new(app, browser: :chrome)
|
537
544
|
end
|
@@ -153,7 +153,9 @@ module Capybara
|
|
153
153
|
# @option offset [Integer] y Y coordinate to offset the click location from the top left corner of the element
|
154
154
|
# @return [Capybara::Node::Element] The element
|
155
155
|
def click(*keys, wait: nil, **offset)
|
156
|
-
|
156
|
+
raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]
|
157
|
+
|
158
|
+
synchronize(wait) { base.click(Array(keys), offset) }
|
157
159
|
self
|
158
160
|
end
|
159
161
|
|
@@ -164,6 +166,8 @@ module Capybara
|
|
164
166
|
# @macro click_modifiers
|
165
167
|
# @return [Capybara::Node::Element] The element
|
166
168
|
def right_click(*keys, wait: nil, **offset)
|
169
|
+
raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]
|
170
|
+
|
167
171
|
synchronize(wait) { base.right_click(keys, offset) }
|
168
172
|
self
|
169
173
|
end
|
@@ -175,6 +179,8 @@ module Capybara
|
|
175
179
|
# @macro click_modifiers
|
176
180
|
# @return [Capybara::Node::Element] The element
|
177
181
|
def double_click(*keys, wait: nil, **offset)
|
182
|
+
raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]
|
183
|
+
|
178
184
|
synchronize(wait) { base.double_click(keys, offset) }
|
179
185
|
self
|
180
186
|
end
|
@@ -546,24 +546,6 @@ module Capybara
|
|
546
546
|
end
|
547
547
|
end
|
548
548
|
|
549
|
-
# Deprecated
|
550
|
-
# TODO: remove
|
551
|
-
def refute_selector(*args, &optional_filter_block)
|
552
|
-
warn '`refute_selector` was never meant to be in this scope unless ' \
|
553
|
-
'using minitest. Either replace with `assert_no_selector` ' \
|
554
|
-
"or require 'capybara/minitest'."
|
555
|
-
assert_no_selector(*args, &optional_filter_block)
|
556
|
-
end
|
557
|
-
|
558
|
-
# Deprecated
|
559
|
-
# TODO: remove
|
560
|
-
def refute_matches_selector(*args, &optional_filter_block)
|
561
|
-
warn '`refute_matches_selector` was never meant to be in this scope unless ' \
|
562
|
-
'using minitest. Either replace with `assert_not_matches_selector` ' \
|
563
|
-
"or require 'capybara/minitest'."
|
564
|
-
assert_not_matches_selector(*args, &optional_filter_block)
|
565
|
-
end
|
566
|
-
|
567
549
|
##
|
568
550
|
#
|
569
551
|
# Checks if the current node matches given selector
|
@@ -79,7 +79,7 @@ module Capybara
|
|
79
79
|
if count
|
80
80
|
message << " #{occurrences count}"
|
81
81
|
elsif between
|
82
|
-
message << " between #{between.first} and #{between.last} times"
|
82
|
+
message << " between #{between.first} and #{between.end ? between.last : 'infinite'} times"
|
83
83
|
elsif maximum
|
84
84
|
message << " at most #{occurrences maximum}"
|
85
85
|
elsif minimum
|
@@ -62,13 +62,11 @@ module Capybara
|
|
62
62
|
|
63
63
|
def matches_filters?(node, node_filter_errors = [])
|
64
64
|
return true if (@resolved_node&.== node) && options[:allow_self]
|
65
|
-
return false unless matches_locator_filter?(node)
|
66
65
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
matches_node_filters?(node, node_filter_errors) && matches_filter_block?(node)
|
66
|
+
matches_locator_filter?(node) &&
|
67
|
+
matches_system_filters?(node) &&
|
68
|
+
matches_node_filters?(node, node_filter_errors) &&
|
69
|
+
matches_filter_block?(node)
|
72
70
|
rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
|
73
71
|
false
|
74
72
|
end
|
@@ -93,11 +91,11 @@ module Capybara
|
|
93
91
|
exact = exact? if exact.nil?
|
94
92
|
expr = apply_expression_filters(@expression)
|
95
93
|
expr = exact ? expr.to_xpath(:exact) : expr.to_s if expr.respond_to?(:to_xpath)
|
96
|
-
|
94
|
+
filtered_expression(expr)
|
97
95
|
end
|
98
96
|
|
99
97
|
def css
|
100
|
-
|
98
|
+
filtered_expression(apply_expression_filters(@expression))
|
101
99
|
end
|
102
100
|
|
103
101
|
# @api private
|
@@ -136,12 +134,10 @@ module Capybara
|
|
136
134
|
end
|
137
135
|
|
138
136
|
def find_selector(locator)
|
139
|
-
|
140
|
-
|
141
|
-
else
|
142
|
-
|
143
|
-
end
|
144
|
-
selector || Selector.all[session_options.default_selector]
|
137
|
+
case locator
|
138
|
+
when Symbol then Selector[locator]
|
139
|
+
else Selector.for(locator)
|
140
|
+
end || Selector[session_options.default_selector]
|
145
141
|
end
|
146
142
|
|
147
143
|
def find_nodes_by_selector_format(node, exact)
|
@@ -165,6 +161,8 @@ module Capybara
|
|
165
161
|
end
|
166
162
|
|
167
163
|
def matches_node_filters?(node, errors)
|
164
|
+
applied_filters << :node
|
165
|
+
|
168
166
|
unapplied_options = options.keys - valid_keys
|
169
167
|
@selector.with_filter_errors(errors) do
|
170
168
|
node_filters.all? do |filter_name, filter|
|
@@ -196,7 +194,7 @@ module Capybara
|
|
196
194
|
end
|
197
195
|
|
198
196
|
def filter_set(name)
|
199
|
-
::Capybara::Selector::FilterSet
|
197
|
+
::Capybara::Selector::FilterSet[name]
|
200
198
|
end
|
201
199
|
|
202
200
|
def node_filters
|
@@ -235,18 +233,11 @@ module Capybara
|
|
235
233
|
raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
|
236
234
|
end
|
237
235
|
|
238
|
-
def
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
def filtered_css(expr)
|
245
|
-
::Capybara::Selector::CSS.split(expr).map do |sel|
|
246
|
-
sel += conditions_from_id if use_default_id_filter?
|
247
|
-
sel += conditions_from_classes if use_default_class_filter?
|
248
|
-
sel
|
249
|
-
end.join(', ')
|
236
|
+
def filtered_expression(expr)
|
237
|
+
conditions = {}
|
238
|
+
conditions[:id] = options[:id] if use_default_id_filter?
|
239
|
+
conditions[:class] = options[:class] if use_default_class_filter?
|
240
|
+
builder(expr).add_attribute_conditions(conditions)
|
250
241
|
end
|
251
242
|
|
252
243
|
def use_default_id_filter?
|
@@ -257,14 +248,6 @@ module Capybara
|
|
257
248
|
options.key?(:class) && !custom_keys.include?(:class)
|
258
249
|
end
|
259
250
|
|
260
|
-
def conditions_from_classes
|
261
|
-
builder.class_conditions(options[:class])
|
262
|
-
end
|
263
|
-
|
264
|
-
def conditions_from_id
|
265
|
-
builder.attribute_conditions(id: options[:id])
|
266
|
-
end
|
267
|
-
|
268
251
|
def apply_expression_filters(expression)
|
269
252
|
unapplied_options = options.keys - valid_keys
|
270
253
|
expression_filters.inject(expression) do |expr, (name, ef)|
|
@@ -307,12 +290,14 @@ module Capybara
|
|
307
290
|
end
|
308
291
|
|
309
292
|
def matches_locator_filter?(node)
|
310
|
-
return true
|
293
|
+
return true unless @selector.locator_filter
|
311
294
|
|
312
|
-
@selector.locator_filter.
|
295
|
+
@selector.locator_filter.matches?(node, @locator, @selector)
|
313
296
|
end
|
314
297
|
|
315
298
|
def matches_system_filters?(node)
|
299
|
+
applied_filters << :system
|
300
|
+
|
316
301
|
matches_id_filter?(node) &&
|
317
302
|
matches_class_filter?(node) &&
|
318
303
|
matches_text_filter?(node) &&
|
@@ -374,8 +359,8 @@ module Capybara
|
|
374
359
|
@selector.default_visibility(session_options.ignore_hidden_elements, options)
|
375
360
|
end
|
376
361
|
|
377
|
-
def builder
|
378
|
-
selector.builder
|
362
|
+
def builder(expr)
|
363
|
+
selector.builder(expr)
|
379
364
|
end
|
380
365
|
end
|
381
366
|
end
|
data/lib/capybara/result.rb
CHANGED
@@ -59,7 +59,7 @@ module Capybara
|
|
59
59
|
nil
|
60
60
|
end
|
61
61
|
when Range
|
62
|
-
idx.max
|
62
|
+
idx.end && idx.max # endless range will have end == nil
|
63
63
|
end
|
64
64
|
|
65
65
|
if max_idx.nil?
|
@@ -92,9 +92,9 @@ module Capybara
|
|
92
92
|
end
|
93
93
|
|
94
94
|
if between
|
95
|
-
min, max = between.
|
96
|
-
size = load_up_to(max + 1)
|
97
|
-
return between.include?(size)
|
95
|
+
min, max = between.min, (between.end && between.max)
|
96
|
+
size = load_up_to(max ? max + 1 : min)
|
97
|
+
return size <=> min unless between.include?(size)
|
98
98
|
end
|
99
99
|
|
100
100
|
0
|
data/lib/capybara/selector.rb
CHANGED
@@ -34,7 +34,7 @@ Capybara.add_selector(:css) do
|
|
34
34
|
end
|
35
35
|
|
36
36
|
Capybara.add_selector(:id) do
|
37
|
-
xpath { |id| XPath.descendant
|
37
|
+
xpath { |id| builder(XPath.descendant).add_attribute_conditions(id: id) }
|
38
38
|
locator_filter { |node, id| id.is_a?(Regexp) ? node[:id] =~ id : true }
|
39
39
|
end
|
40
40
|
|
@@ -92,8 +92,7 @@ end
|
|
92
92
|
|
93
93
|
Capybara.add_selector(:link) do
|
94
94
|
xpath do |locator, href: true, alt: nil, title: nil, **|
|
95
|
-
xpath = XPath.descendant(:a)
|
96
|
-
xpath = xpath[@href_conditions = builder.attribute_conditions(href: href)]
|
95
|
+
xpath = builder(XPath.descendant(:a)).add_attribute_conditions(href: href)
|
97
96
|
|
98
97
|
unless locator.nil?
|
99
98
|
locator = locator.to_s
|
@@ -119,25 +118,18 @@ Capybara.add_selector(:link) do
|
|
119
118
|
end
|
120
119
|
|
121
120
|
expression_filter(:download, valid_values: [true, false, String]) do |expr, download|
|
122
|
-
expr
|
121
|
+
builder(expr).add_attribute_conditions(download: download)
|
123
122
|
end
|
124
123
|
|
125
124
|
describe_expression_filters do |**options|
|
126
125
|
desc = +''
|
127
126
|
if (href = options[:href])
|
128
|
-
if
|
129
|
-
|
130
|
-
|
131
|
-
desc << " with href matching #{href.inspect}"
|
132
|
-
end
|
127
|
+
desc << " with href #{'matching ' if href.is_a? Regexp}#{href.inspect}"
|
128
|
+
elsif options.key?(:href) # is nil/false specified?
|
129
|
+
desc << ' with no href attribute'
|
133
130
|
end
|
134
|
-
desc << ' with no href attribute' if options.fetch(:href, true).nil?
|
135
131
|
desc
|
136
132
|
end
|
137
|
-
|
138
|
-
describe_node_filters do |href: nil, **|
|
139
|
-
" with href matching #{href.inspect}" if href.is_a?(Regexp) && @href_conditions.nil?
|
140
|
-
end
|
141
133
|
end
|
142
134
|
|
143
135
|
Capybara.add_selector(:button) do
|
@@ -489,7 +481,7 @@ Capybara.add_selector(:element) do
|
|
489
481
|
end
|
490
482
|
|
491
483
|
expression_filter(:attributes, matcher: /.+/) do |xpath, name, val|
|
492
|
-
xpath
|
484
|
+
builder(xpath).add_attribute_conditions(name => val)
|
493
485
|
end
|
494
486
|
|
495
487
|
node_filter(:attributes, matcher: /.+/) do |node, name, val|
|
@@ -6,41 +6,73 @@ module Capybara
|
|
6
6
|
class Selector
|
7
7
|
# @api private
|
8
8
|
class CSSBuilder
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
def initialize(expression)
|
10
|
+
@expression = expression || ''
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :expression
|
14
|
+
|
15
|
+
def add_attribute_conditions(**attributes)
|
16
|
+
@expression = attributes.inject(expression) do |css, (name, value)|
|
17
|
+
conditions = if name == :class
|
18
|
+
class_conditions(value)
|
19
|
+
elsif value.is_a? Regexp
|
20
|
+
Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs|
|
21
|
+
strs.map do |str|
|
22
|
+
"[#{name}*='#{str}'#{' i' if value.casefold?}]"
|
18
23
|
end.join
|
19
|
-
when true
|
20
|
-
"[#{attribute}]"
|
21
|
-
when false
|
22
|
-
':not([attribute])'
|
23
|
-
else
|
24
|
-
if attribute == :id
|
25
|
-
"##{::Capybara::Selector::CSS.escape(value)}"
|
26
|
-
else
|
27
|
-
"[#{attribute}='#{value}']"
|
28
|
-
end
|
29
24
|
end
|
30
|
-
|
25
|
+
else
|
26
|
+
[attribute_conditions(name => value)]
|
27
|
+
end
|
28
|
+
|
29
|
+
::Capybara::Selector::CSS.split(css).map do |sel|
|
30
|
+
next sel if conditions.empty?
|
31
|
+
|
32
|
+
conditions.map { |cond| sel + cond }.join(', ')
|
33
|
+
end.join(', ')
|
31
34
|
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
32
38
|
|
33
|
-
|
34
|
-
|
39
|
+
def attribute_conditions(attributes)
|
40
|
+
attributes.map do |attribute, value|
|
41
|
+
case value
|
35
42
|
when XPath::Expression
|
36
|
-
raise ArgumentError,
|
43
|
+
raise ArgumentError, "XPath expressions are not supported for the :#{attribute} filter with CSS based selectors"
|
37
44
|
when Regexp
|
38
|
-
|
45
|
+
Selector::RegexpDisassembler.new(value).substrings.map do |str|
|
46
|
+
"[#{attribute}*='#{str}'#{' i' if value.casefold?}]"
|
47
|
+
end.join
|
48
|
+
when true
|
49
|
+
"[#{attribute}]"
|
50
|
+
when false
|
51
|
+
':not([attribute])'
|
39
52
|
else
|
40
|
-
|
41
|
-
|
42
|
-
|
53
|
+
if attribute == :id
|
54
|
+
"##{::Capybara::Selector::CSS.escape(value)}"
|
55
|
+
else
|
56
|
+
"[#{attribute}='#{value}']"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end.join
|
60
|
+
end
|
61
|
+
|
62
|
+
def class_conditions(classes)
|
63
|
+
case classes
|
64
|
+
when XPath::Expression
|
65
|
+
raise ArgumentError, 'XPath expressions are not supported for the :class filter with CSS based selectors'
|
66
|
+
when Regexp
|
67
|
+
Selector::RegexpDisassembler.new(classes).alternated_substrings.map do |strs|
|
68
|
+
strs.map do |str|
|
69
|
+
"[class*='#{str}'#{' i' if classes.casefold?}]"
|
70
|
+
end.join
|
43
71
|
end
|
72
|
+
else
|
73
|
+
cls = Array(classes).group_by { |cl| cl.start_with?('!') && !cl.start_with?('!!!') }
|
74
|
+
[(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
|
75
|
+
cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
|
44
76
|
end
|
45
77
|
end
|
46
78
|
end
|