capybara 3.1.1 → 3.2.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.
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