capybara 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +19 -0
  3. data/README.md +1 -1
  4. data/lib/capybara.rb +2 -0
  5. data/lib/capybara/config.rb +2 -1
  6. data/lib/capybara/driver/base.rb +1 -1
  7. data/lib/capybara/driver/node.rb +3 -3
  8. data/lib/capybara/node/actions.rb +90 -92
  9. data/lib/capybara/node/base.rb +2 -2
  10. data/lib/capybara/node/document_matchers.rb +5 -5
  11. data/lib/capybara/node/element.rb +47 -16
  12. data/lib/capybara/node/finders.rb +13 -13
  13. data/lib/capybara/node/matchers.rb +18 -17
  14. data/lib/capybara/node/simple.rb +6 -2
  15. data/lib/capybara/queries/ancestor_query.rb +1 -1
  16. data/lib/capybara/queries/base_query.rb +3 -3
  17. data/lib/capybara/queries/current_path_query.rb +1 -1
  18. data/lib/capybara/queries/match_query.rb +8 -0
  19. data/lib/capybara/queries/selector_query.rb +97 -42
  20. data/lib/capybara/queries/sibling_query.rb +1 -1
  21. data/lib/capybara/queries/text_query.rb +12 -7
  22. data/lib/capybara/rack_test/browser.rb +9 -7
  23. data/lib/capybara/rack_test/form.rb +15 -17
  24. data/lib/capybara/rack_test/node.rb +12 -12
  25. data/lib/capybara/result.rb +26 -15
  26. data/lib/capybara/rspec.rb +1 -2
  27. data/lib/capybara/rspec/compound.rb +4 -4
  28. data/lib/capybara/rspec/matchers.rb +2 -2
  29. data/lib/capybara/selector.rb +75 -225
  30. data/lib/capybara/selector/css.rb +2 -2
  31. data/lib/capybara/selector/filter_set.rb +17 -21
  32. data/lib/capybara/selector/filters/base.rb +24 -1
  33. data/lib/capybara/selector/filters/expression_filter.rb +3 -5
  34. data/lib/capybara/selector/filters/node_filter.rb +4 -4
  35. data/lib/capybara/selector/selector.rb +221 -69
  36. data/lib/capybara/selenium/driver.rb +15 -88
  37. data/lib/capybara/selenium/node.rb +25 -28
  38. data/lib/capybara/server.rb +10 -54
  39. data/lib/capybara/server/animation_disabler.rb +43 -0
  40. data/lib/capybara/server/middleware.rb +55 -0
  41. data/lib/capybara/session.rb +29 -30
  42. data/lib/capybara/session/config.rb +11 -1
  43. data/lib/capybara/session/matchers.rb +5 -5
  44. data/lib/capybara/spec/session/assert_text_spec.rb +1 -1
  45. data/lib/capybara/spec/session/body_spec.rb +10 -12
  46. data/lib/capybara/spec/session/click_link_spec.rb +3 -3
  47. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +1 -1
  48. data/lib/capybara/spec/session/fill_in_spec.rb +9 -0
  49. data/lib/capybara/spec/session/find_field_spec.rb +1 -1
  50. data/lib/capybara/spec/session/find_spec.rb +8 -3
  51. data/lib/capybara/spec/session/has_link_spec.rb +2 -2
  52. data/lib/capybara/spec/session/node_spec.rb +50 -0
  53. data/lib/capybara/spec/session/node_wrapper_spec.rb +5 -5
  54. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +1 -1
  55. data/lib/capybara/spec/session/window/windows_spec.rb +3 -5
  56. data/lib/capybara/spec/spec_helper.rb +4 -2
  57. data/lib/capybara/spec/views/with_animation.erb +46 -0
  58. data/lib/capybara/version.rb +1 -1
  59. data/lib/capybara/window.rb +3 -2
  60. data/spec/filter_set_spec.rb +19 -2
  61. data/spec/result_spec.rb +33 -1
  62. data/spec/rspec/features_spec.rb +6 -10
  63. data/spec/rspec/shared_spec_matchers.rb +4 -4
  64. data/spec/selector_spec.rb +74 -4
  65. data/spec/selenium_spec_marionette.rb +2 -0
  66. data/spec/server_spec.rb +1 -1
  67. data/spec/session_spec.rb +12 -0
  68. data/spec/shared_selenium_session.rb +30 -0
  69. metadata +8 -9
  70. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  71. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  72. data/.yard/yard_extensions.rb +0 -78
  73. data/.yardopts +0 -1
@@ -5,7 +5,7 @@ module Capybara
5
5
  class CSS
6
6
  def self.escape(str)
7
7
  value = str.dup
8
- out = "".dup
8
+ out = +""
9
9
  out << value.slice!(0...1) if value =~ /^[-_]/
10
10
  out << (value[0] =~ NMSTART ? value.slice!(0...1) : escape_char(value.slice!(0...1)))
11
11
  out << value.gsub(/[^a-zA-Z0-9_-]/) { |c| escape_char c }
@@ -16,7 +16,7 @@ module Capybara
16
16
  c =~ %r{[ -/:-~]} ? "\\#{c}" : format("\\%06x", c.ord)
17
17
  end
18
18
 
19
- S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'.freeze
19
+ S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
20
20
  H = /[0-9a-fA-F]/
21
21
  UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/
22
22
  NONASCII = /[#{S}]/
@@ -5,17 +5,20 @@ require 'capybara/selector/filter'
5
5
  module Capybara
6
6
  class Selector
7
7
  class FilterSet
8
- attr_reader :descriptions
8
+ attr_reader :descriptions, :node_filters, :expression_filters
9
9
 
10
10
  def initialize(name, &block)
11
11
  @name = name
12
12
  @descriptions = []
13
+ @expression_filters = {}
14
+ @node_filters = {}
13
15
  instance_eval(&block)
14
16
  end
15
17
 
16
- def filter(name, *types_and_options, &block)
18
+ def node_filter(name, *types_and_options, &block)
17
19
  add_filter(name, Filters::NodeFilter, *types_and_options, &block)
18
20
  end
21
+ alias_method :filter, :node_filter
19
22
 
20
23
  def expression_filter(name, *types_and_options, &block)
21
24
  add_filter(name, Filters::ExpressionFilter, *types_and_options, &block)
@@ -27,21 +30,7 @@ module Capybara
27
30
 
28
31
  def description(**options)
29
32
  opts = options_with_defaults(options)
30
- @descriptions.map do |desc|
31
- desc.call(opts).to_s
32
- end.join
33
- end
34
-
35
- def filters
36
- @filters ||= {}
37
- end
38
-
39
- def node_filters
40
- filters.reject { |_n, f| f.nil? || f.is_a?(Filters::ExpressionFilter) }.freeze
41
- end
42
-
43
- def expression_filters
44
- filters.select { |_n, f| f.nil? || f.is_a?(Filters::ExpressionFilter) }.freeze
33
+ @descriptions.map { |desc| desc.call(opts).to_s }.join
45
34
  end
46
35
 
47
36
  class << self
@@ -62,15 +51,22 @@ module Capybara
62
51
 
63
52
  def options_with_defaults(options)
64
53
  options = options.dup
65
- filters.each do |name, filter|
66
- options[name] = filter.default if filter.default? && !options.key?(name)
54
+ [expression_filters, node_filters].each do |filters|
55
+ filters.select { |_n, f| f.default? }.each do |name, filter|
56
+ options[name] = filter.default unless options.key?(name)
57
+ end
67
58
  end
68
59
  options
69
60
  end
70
61
 
71
- def add_filter(name, filter_class, *types, **options, &block)
62
+ def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
72
63
  types.each { |k| options[k] = true }
73
- filters[name] = filter_class.new(name, block, options)
64
+ raise "ArgumentError", ":default option is not supported for filters with a :matcher option" if matcher && options[:default]
65
+ if filter_class <= Filters::ExpressionFilter
66
+ @expression_filters[name] = filter_class.new(name, matcher, block, options)
67
+ else
68
+ @node_filters[name] = filter_class.new(name, matcher, block, options)
69
+ end
74
70
  end
75
71
  end
76
72
  end
@@ -4,8 +4,9 @@ module Capybara
4
4
  class Selector
5
5
  module Filters
6
6
  class Base
7
- def initialize(name, block, **options)
7
+ def initialize(name, matcher, block, **options)
8
8
  @name = name
9
+ @matcher = matcher
9
10
  @block = block
10
11
  @options = options
11
12
  @options[:valid_values] = [true, false] if options[:boolean]
@@ -23,8 +24,30 @@ module Capybara
23
24
  @options.key?(:skip_if) && value == @options[:skip_if]
24
25
  end
25
26
 
27
+ def matcher?
28
+ !@matcher.nil?
29
+ end
30
+
31
+ def handles_option?(option_name)
32
+ if matcher?
33
+ option_name =~ @matcher
34
+ else
35
+ @name == option_name
36
+ end
37
+ end
38
+
26
39
  private
27
40
 
41
+ def apply(subject, name, value, skip_value)
42
+ return skip_value if skip?(value)
43
+ raise ArgumentError, "Invalid value #{value.inspect} passed to #{self.class.name.split('::').last} #{name}#{" : #{@name}" if @name.is_a?(Regexp)}" unless valid_value?(value)
44
+ if @block.arity == 2
45
+ @block.call(subject, value)
46
+ else
47
+ @block.call(subject, name, value)
48
+ end
49
+ end
50
+
28
51
  def valid_value?(value)
29
52
  !@options.key?(:valid_values) || Array(@options[:valid_values]).include?(value)
30
53
  end
@@ -6,17 +6,15 @@ module Capybara
6
6
  class Selector
7
7
  module Filters
8
8
  class ExpressionFilter < Base
9
- def apply_filter(expr, value)
10
- return expr if skip?(value)
11
- raise "ArgumentError", "Invalid value #{value.inspect} passed to expression filter #{@name}" unless valid_value?(value)
12
- @block.call(expr, value)
9
+ def apply_filter(expr, name, value)
10
+ apply(expr, name, value, expr)
13
11
  end
14
12
  end
15
13
 
16
14
  class IdentityExpressionFilter < ExpressionFilter
17
15
  def initialize; end
18
16
  def default?; false; end
19
- def apply_filter(expr, _value); expr; end
17
+ def apply_filter(expr, _name, _value); expr; end
20
18
  end
21
19
  end
22
20
  end
@@ -6,10 +6,10 @@ module Capybara
6
6
  class Selector
7
7
  module Filters
8
8
  class NodeFilter < Base
9
- def matches?(node, value)
10
- return true if skip?(value)
11
- raise ArgumentError, "Invalid value #{value.inspect} passed to filter #{@name}" unless valid_value?(value)
12
- @block.call(node, value)
9
+ def matches?(node, name, value)
10
+ apply(node, name, value, true)
11
+ rescue Capybara::ElementNotFound
12
+ false
13
13
  end
14
14
  end
15
15
  end
@@ -1,27 +1,165 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Style/AsciiComments
4
+
3
5
  require 'capybara/selector/filter_set'
4
6
  require 'capybara/selector/css'
5
- require 'xpath'
6
-
7
- # Patch XPath to allow a nil condition in where
8
- module XPath
9
- class Renderer
10
- undef :where if method_defined?(:where)
11
- def where(on, condition)
12
- condition = condition.to_s
13
- if !condition.empty?
14
- "#{on}[#{condition}]"
15
- else
16
- on.to_s
17
- end
18
- end
19
- end
20
- end
21
7
 
22
8
  module Capybara
9
+ #
10
+ # ## Built-in Selectors
11
+ #
12
+ # * **:xpath** - Select elements by XPath expression
13
+ # * Locator: An XPath expression
14
+ #
15
+ # * **:css** - Select elements by CSS selector
16
+ # * Locator: A CSS selector
17
+ #
18
+ # * **:id** - Select element by id
19
+ # * Locator: The id of the element to match
20
+ #
21
+ # * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
22
+ # * Locator: Matches against the id, name, or placeholder
23
+ # * Filters:
24
+ # * :id (String) — Matches the id attribute
25
+ # * :name (String) — Matches the name attribute
26
+ # * :placeholder (String) — Matches the placeholder attribute
27
+ # * :type (String) — Matches the type attribute of the field or element type for 'textarea' and 'select'
28
+ # * :readonly (Boolean)
29
+ # * :with (String) — Matches the current value of the field
30
+ # * :class (String, Array<String>) — Matches the class(es) provided
31
+ # * :checked (Boolean) — Match checked fields?
32
+ # * :unchecked (Boolean) — Match unchecked fields?
33
+ # * :disabled (Boolean) — Match disabled field?
34
+ # * :multiple (Boolean) — Match fields that accept multiple values
35
+ #
36
+ # * **:fieldset** - Select fieldset elements
37
+ # * Locator: Matches id or contents of wrapped legend
38
+ # * Filters:
39
+ # * :id (String) — Matches id attribute
40
+ # * :legend (String) — Matches contents of wrapped legend
41
+ # * :class (String, Array<String>) — Matches the class(es) provided
42
+ #
43
+ # * **:link** - Find links ( <a> elements with an href attribute )
44
+ # * Locator: Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
45
+ # * Filters:
46
+ # * :id (String) — Matches the id attribute
47
+ # * :title (String) — Matches the title attribute
48
+ # * :alt (String) — Matches the alt attribute of a contained img element
49
+ # * :class (String) — Matches the class(es) provided
50
+ # * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
51
+ #
52
+ # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
53
+ # * Locator: Matches the id, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
54
+ # * Filters:
55
+ # * :id (String) — Matches the id attribute
56
+ # * :title (String) — Matches the title attribute
57
+ # * :class (String) — Matches the class(es) provided
58
+ # * :value (String) — Matches the value of an input button
59
+ # * :type
60
+ #
61
+ # * **:link_or_button** - Find links or buttons
62
+ # * Locator: See :link and :button selectors
63
+ #
64
+ # * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
65
+ # * Locator: Matches against the id, name, or placeholder
66
+ # * Filters:
67
+ # * :id (String) — Matches the id attribute
68
+ # * :name (String) — Matches the name attribute
69
+ # * :placeholder (String) — Matches the placeholder attribute
70
+ # * :with (String) — Matches the current value of the field
71
+ # * :type (String) — Matches the type attribute of the field or element type for 'textarea'
72
+ # * :class (String, Array<String>) — Matches the class(es) provided
73
+ # * :disabled (Boolean) — Match disabled field?
74
+ # * :multiple (Boolean) — Match fields that accept multiple values
75
+ #
76
+ # * **:radio_button** - Find radio buttons
77
+ # * Locator: Match id, name, or associated label text
78
+ # * Filters:
79
+ # * :id (String) — Matches the id attribute
80
+ # * :name (String) — Matches the name attribute
81
+ # * :class (String, Array<String>) — Matches the class(es) provided
82
+ # * :checked (Boolean) — Match checked fields?
83
+ # * :unchecked (Boolean) — Match unchecked fields?
84
+ # * :disabled (Boolean) — Match disabled field?
85
+ # * :option (String) — Match the value
86
+ #
87
+ # * **:checkbox** - Find checkboxes
88
+ # * Locator: Match id, name, or associated label text
89
+ # * Filters:
90
+ # * *:id (String) — Matches the id attribute
91
+ # * *:name (String) — Matches the name attribute
92
+ # * *:class (String, Array<String>) — Matches the class(es) provided
93
+ # * *:checked (Boolean) — Match checked fields?
94
+ # * *:unchecked (Boolean) — Match unchecked fields?
95
+ # * *:disabled (Boolean) — Match disabled field?
96
+ # * *:option (String) — Match the value
97
+ #
98
+ # * **:select** - Find select elements
99
+ # * Locator: Match id, name, placeholder, or associated label text
100
+ # * Filters:
101
+ # * :id (String) — Matches the id attribute
102
+ # * :name (String) — Matches the name attribute
103
+ # * :placeholder (String) — Matches the placeholder attribute
104
+ # * :class (String, Array<String>) — Matches the class(es) provided
105
+ # * :disabled (Boolean) — Match disabled field?
106
+ # * :multiple (Boolean) — Match fields that accept multiple values
107
+ # * :options (Array<String>) — Exact match options
108
+ # * :with_options (Array<String>) — Partial match options
109
+ # * :selected (String, Array<String>) — Match the selection(s)
110
+ # * :with_selected (String, Array<String>) — Partial match the selection(s)
111
+ #
112
+ # * **:option** - Find option elements
113
+ # * Locator: Match text of option
114
+ # * Filters:
115
+ # * :disabled (Boolean) — Match disabled option
116
+ # * :selected (Boolean) — Match selected option
117
+ #
118
+ # * **:datalist_input**
119
+ # * Locator:
120
+ # * Filters:
121
+ # * :disabled
122
+ # * :name
123
+ # * :placeholder
124
+ #
125
+ # * **:datalist_option**
126
+ # * Locator:
127
+ #
128
+ # * **:file_field** - Find file input elements
129
+ # * Locator: Match id, name, or associated label text
130
+ # * Filters:
131
+ # * :id (String) — Matches the id attribute
132
+ # * :name (String) — Matches the name attribute
133
+ # * :class (String, Array<String>) — Matches the class(es) provided
134
+ # * :disabled (Boolean) — Match disabled field?
135
+ # * :multiple (Boolean) — Match field that accepts multiple values
136
+ #
137
+ # * **:label** - Find label elements
138
+ # * Locator: Match id or text contents
139
+ # * Filters:
140
+ # * :for (Element, String) — The element or id of the element associated with the label
141
+ #
142
+ # * **:table** - Find table elements
143
+ # * Locator: id or caption text of table
144
+ # * Filters:
145
+ # * :id (String) — Match id attribute of table
146
+ # * :caption (String) — Match text of associated caption
147
+ # * :class (String, Array<String>) — Matches the class(es) provided
148
+ #
149
+ # * **:frame** - Find frame/iframe elements
150
+ # * Locator: Match id or name
151
+ # * Filters:
152
+ # * :id (String) — Match id attribute
153
+ # * :name (String) — Match name attribute
154
+ # * :class (String, Array<String>) — Matches the class(es) provided
155
+ #
156
+ # * **:element**
157
+ # * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
158
+ # * Filters: Matches on any element attribute
159
+ #
23
160
  class Selector
24
161
  attr_reader :name, :format
162
+ extend Forwardable
25
163
 
26
164
  class << self
27
165
  def all
@@ -56,7 +194,8 @@ module Capybara
56
194
  end
57
195
 
58
196
  def custom_filters
59
- @filter_set.filters
197
+ warn "Deprecated: Selector#custom_filters is not valid when same named expression and node filter exist - don't use"
198
+ node_filters.merge(expression_filters).freeze
60
199
  end
61
200
 
62
201
  def node_filters
@@ -81,10 +220,10 @@ module Capybara
81
220
  # @overload xpath()
82
221
  # @return [#call] The block that will be called to generate the XPath expression
83
222
  #
84
- def xpath(*expression_filters, &block)
223
+ def xpath(*allowed_filters, &block)
85
224
  if block
86
225
  @format, @expression = :xpath, block
87
- expression_filters.flatten.each { |ef| custom_filters[ef] = Filters::IdentityExpressionFilter.new }
226
+ allowed_filters.flatten.each { |ef| expression_filters[ef] = Filters::IdentityExpressionFilter.new }
88
227
  end
89
228
  format == :xpath ? @expression : nil
90
229
  end
@@ -103,10 +242,10 @@ module Capybara
103
242
  # @overload css()
104
243
  # @return [#call] The block that will be called to generate the CSS selector
105
244
  #
106
- def css(*expression_filters, &block)
245
+ def css(*allowed_filters, &block)
107
246
  if block
108
247
  @format, @expression = :css, block
109
- expression_filters.flatten.each { |ef| custom_filters[ef] = nil }
248
+ allowed_filters.flatten.each { |ef| expression_filters[ef] = nil }
110
249
  end
111
250
  format == :css ? @expression : nil
112
251
  end
@@ -143,12 +282,10 @@ module Capybara
143
282
  #
144
283
  # Description of the selector
145
284
  #
146
- # @param [Hash] options The options of the query used to generate the description
147
- # @return [String] Description of the selector when used with the options passed
148
- #
149
- def description(**options)
150
- @filter_set.description(options)
151
- end
285
+ # @!method description(options)
286
+ # @param [Hash] options The options of the query used to generate the description
287
+ # @return [String] Description of the selector when used with the options passed
288
+ def_delegator :@filter_set, :description
152
289
 
153
290
  def call(locator, **options)
154
291
  if format
@@ -168,41 +305,58 @@ module Capybara
168
305
  # @return [Boolean] Whether or not to use this selector
169
306
  #
170
307
  def match?(locator)
171
- @match and @match.call(locator)
308
+ @match&.call(locator)
172
309
  end
173
310
 
174
311
  ##
175
312
  #
176
- # Define a non-expression filter for use with this selector
313
+ # Define a node filter for use with this selector
177
314
  #
178
- # @overload filter(name, *types, options={}, &block)
179
- # @param [Symbol] name The filter name
315
+ # @!method node_filter(name, *types, options={}, &block)
316
+ # @param [Symbol, Regexp] name The filter name
180
317
  # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
181
318
  # @param [Hash] options ({}) Options of the filter
182
319
  # @option options [Array<>] :valid_values Valid values for this filter
183
320
  # @option options :default The default value of the filter (if any)
184
321
  # @option options :skip_if Value of the filter that will cause it to be skipped
322
+ # @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
185
323
  #
186
- def filter(name, *types_and_options, &block)
187
- add_filter(name, Filters::NodeFilter, *types_and_options, &block)
188
- end
324
+ # If a Symbol is passed for the name the block should accept | node, option_value |, while if a Regexp
325
+ # is passed for the name the block should accept | node, option_name, option_value |. In either case
326
+ # the block should return `true` if the node passes the filer or `false` if it doesn't
189
327
 
190
- def expression_filter(name, *types_and_options, &block)
191
- add_filter(name, Filters::ExpressionFilter, *types_and_options, &block)
192
- end
328
+ # @!method filter
329
+ # See {Selector#node_filter}
330
+
331
+ ##
332
+ #
333
+ # Define an expression filter for use with this selector
334
+ #
335
+ # @!method expression_filter(name, *types, options={}, &block)
336
+ # @param [Symbol, Regexp] name The filter name
337
+ # @param [Regexp] matcher (nil) A Regexp used to check whether a specific option is handled by this filter
338
+ # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
339
+ # @param [Hash] options ({}) Options of the filter
340
+ # @option options [Array<>] :valid_values Valid values for this filter
341
+ # @option options :default The default value of the filter (if any)
342
+ # @option options :skip_if Value of the filter that will cause it to be skipped
343
+ # @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
344
+ #
345
+ # If a Symbol is passed for the name the block should accept | current_expression, option_value |, while if a Regexp
346
+ # is passed for the name the block should accept | current_expression, option_name, option_value |. In either case
347
+ # the block should return the modified expression
348
+
349
+ def_delegators :@filter_set, :node_filter, :expression_filter, :filter
193
350
 
194
351
  def filter_set(name, filters_to_use = nil)
195
352
  f_set = FilterSet.all[name]
196
- f_set.filters.each do |n, filter|
197
- custom_filters[n] = filter if filters_to_use.nil? || filters_to_use.include?(n)
198
- end
199
-
353
+ filter_selector = filters_to_use.nil? ? ->(*) { true } : ->(n, _) { filters_to_use.include? n }
354
+ @filter_set.expression_filters.merge!(f_set.expression_filters.select(&filter_selector))
355
+ @filter_set.node_filters.merge!(f_set.node_filters.select(&filter_selector))
200
356
  f_set.descriptions.each { |desc| @filter_set.describe(&desc) }
201
357
  end
202
358
 
203
- def describe(&block)
204
- @filter_set.describe(&block)
205
- end
359
+ def_delegator :@filter_set, :describe
206
360
 
207
361
  ##
208
362
  #
@@ -218,40 +372,36 @@ module Capybara
218
372
  end
219
373
 
220
374
  def default_visibility(fallback = Capybara.ignore_hidden_elements)
221
- if @default_visibility.nil?
222
- fallback
223
- else
224
- @default_visibility
225
- end
375
+ return @default_visibility unless @default_visibility.nil?
376
+ fallback
226
377
  end
227
378
 
228
379
  private
229
380
 
230
- def add_filter(name, filter_class, *types, **options, &block)
231
- types.each { |k| options[k] = true }
232
- custom_filters[name] = filter_class.new(name, block, options)
233
- end
234
-
235
381
  def locate_field(xpath, locator, enable_aria_label: false, **_options)
382
+ return xpath if locator.nil?
236
383
  locate_xpath = xpath # Need to save original xpath for the label wrap
237
- if locator
238
- locator = locator.to_s
239
- attr_matchers = [XPath.attr(:id) == locator,
240
- XPath.attr(:name) == locator,
241
- XPath.attr(:placeholder) == locator,
242
- XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
243
- attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
244
-
245
- locate_xpath = locate_xpath[attr_matchers]
246
- locate_xpath = locate_xpath.union(XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath))
247
- end
248
-
249
- # locate_xpath = [:name, :placeholder].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
250
- locate_xpath
384
+ locator = locator.to_s
385
+ attr_matchers = [XPath.attr(:id) == locator,
386
+ XPath.attr(:name) == locator,
387
+ XPath.attr(:placeholder) == locator,
388
+ XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
389
+ attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
390
+
391
+ locate_xpath = locate_xpath[attr_matchers]
392
+ locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
251
393
  end
252
394
 
253
395
  def describe_all_expression_filters(**opts)
254
- expression_filters.keys.map { |ef| " with #{ef} #{opts[ef]}" if opts.key?(ef) }.join
396
+ expression_filters.map do |ef_name, ef|
397
+ if ef.matcher?
398
+ opts.keys.map do |k|
399
+ " with #{ef_name}[#{k} => #{opts[k]}]" if ef.handles_option?(k) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(k)
400
+ end.join
401
+ elsif opts.key?(ef_name)
402
+ " with #{ef_name} #{opts[ef_name]}"
403
+ end
404
+ end.join
255
405
  end
256
406
 
257
407
  def find_by_attr(attribute, value)
@@ -268,3 +418,5 @@ module Capybara
268
418
  end
269
419
  end
270
420
  end
421
+
422
+ # rubocop:enable Style/AsciiComments