capybara 3.16.1 → 3.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +321 -0
  4. data/README.md +51 -60
  5. data/lib/capybara.rb +71 -114
  6. data/lib/capybara/config.rb +8 -5
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/node.rb +15 -3
  9. data/lib/capybara/dsl.rb +10 -2
  10. data/lib/capybara/helpers.rb +5 -3
  11. data/lib/capybara/minitest.rb +242 -141
  12. data/lib/capybara/minitest/spec.rb +159 -90
  13. data/lib/capybara/node/actions.rb +85 -74
  14. data/lib/capybara/node/base.rb +4 -4
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +3 -3
  17. data/lib/capybara/node/element.rb +216 -117
  18. data/lib/capybara/node/finders.rb +65 -65
  19. data/lib/capybara/node/matchers.rb +228 -126
  20. data/lib/capybara/node/simple.rb +9 -4
  21. data/lib/capybara/queries/ancestor_query.rb +5 -7
  22. data/lib/capybara/queries/base_query.rb +2 -1
  23. data/lib/capybara/queries/current_path_query.rb +1 -1
  24. data/lib/capybara/queries/selector_query.rb +296 -30
  25. data/lib/capybara/queries/sibling_query.rb +5 -4
  26. data/lib/capybara/queries/style_query.rb +2 -2
  27. data/lib/capybara/queries/text_query.rb +13 -1
  28. data/lib/capybara/queries/title_query.rb +1 -1
  29. data/lib/capybara/rack_test/browser.rb +7 -2
  30. data/lib/capybara/rack_test/driver.rb +1 -1
  31. data/lib/capybara/rack_test/form.rb +1 -1
  32. data/lib/capybara/rack_test/node.rb +43 -7
  33. data/lib/capybara/registration_container.rb +44 -0
  34. data/lib/capybara/registrations/drivers.rb +36 -0
  35. data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
  36. data/lib/capybara/registrations/servers.rb +44 -0
  37. data/lib/capybara/result.rb +36 -8
  38. data/lib/capybara/rspec/matcher_proxies.rb +6 -4
  39. data/lib/capybara/rspec/matchers.rb +100 -63
  40. data/lib/capybara/rspec/matchers/base.rb +23 -10
  41. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  42. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  43. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  44. data/lib/capybara/rspec/matchers/have_selector.rb +16 -8
  45. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  46. data/lib/capybara/rspec/matchers/have_text.rb +4 -4
  47. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  48. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  49. data/lib/capybara/rspec/matchers/match_style.rb +2 -2
  50. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  51. data/lib/capybara/selector.rb +219 -588
  52. data/lib/capybara/selector/builders/css_builder.rb +10 -6
  53. data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
  54. data/lib/capybara/selector/css.rb +4 -2
  55. data/lib/capybara/selector/definition.rb +277 -0
  56. data/lib/capybara/selector/definition/button.rb +52 -0
  57. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  58. data/lib/capybara/selector/definition/css.rb +10 -0
  59. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  60. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  61. data/lib/capybara/selector/definition/element.rb +27 -0
  62. data/lib/capybara/selector/definition/field.rb +40 -0
  63. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  64. data/lib/capybara/selector/definition/file_field.rb +13 -0
  65. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  66. data/lib/capybara/selector/definition/frame.rb +17 -0
  67. data/lib/capybara/selector/definition/id.rb +6 -0
  68. data/lib/capybara/selector/definition/label.rb +62 -0
  69. data/lib/capybara/selector/definition/link.rb +54 -0
  70. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  71. data/lib/capybara/selector/definition/option.rb +27 -0
  72. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  73. data/lib/capybara/selector/definition/select.rb +81 -0
  74. data/lib/capybara/selector/definition/table.rb +109 -0
  75. data/lib/capybara/selector/definition/table_row.rb +21 -0
  76. data/lib/capybara/selector/definition/xpath.rb +5 -0
  77. data/lib/capybara/selector/filter_set.rb +13 -9
  78. data/lib/capybara/selector/filters/base.rb +11 -2
  79. data/lib/capybara/selector/filters/locator_filter.rb +13 -3
  80. data/lib/capybara/selector/regexp_disassembler.rb +9 -2
  81. data/lib/capybara/selector/selector.rb +43 -448
  82. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  83. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  84. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  85. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  86. data/lib/capybara/selenium/driver.rb +125 -56
  87. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +73 -17
  88. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  89. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +41 -2
  90. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +14 -1
  91. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +14 -5
  92. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  93. data/lib/capybara/selenium/extensions/find.rb +67 -45
  94. data/lib/capybara/selenium/extensions/html5_drag.rb +152 -36
  95. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  96. data/lib/capybara/selenium/logger_suppressor.rb +34 -0
  97. data/lib/capybara/selenium/node.rb +227 -56
  98. data/lib/capybara/selenium/nodes/chrome_node.rb +93 -8
  99. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  100. data/lib/capybara/selenium/nodes/firefox_node.rb +37 -59
  101. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  102. data/lib/capybara/selenium/nodes/safari_node.rb +27 -54
  103. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  104. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  105. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  106. data/lib/capybara/selenium/patches/logs.rb +45 -0
  107. data/lib/capybara/server.rb +19 -3
  108. data/lib/capybara/server/animation_disabler.rb +2 -2
  109. data/lib/capybara/server/checker.rb +6 -2
  110. data/lib/capybara/server/middleware.rb +23 -13
  111. data/lib/capybara/session.rb +124 -106
  112. data/lib/capybara/session/config.rb +12 -10
  113. data/lib/capybara/session/matchers.rb +6 -6
  114. data/lib/capybara/spec/public/offset.js +6 -0
  115. data/lib/capybara/spec/public/test.js +94 -5
  116. data/lib/capybara/spec/session/all_spec.rb +84 -6
  117. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  118. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  119. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  120. data/lib/capybara/spec/session/attach_file_spec.rb +14 -6
  121. data/lib/capybara/spec/session/check_spec.rb +10 -4
  122. data/lib/capybara/spec/session/choose_spec.rb +8 -2
  123. data/lib/capybara/spec/session/click_button_spec.rb +44 -1
  124. data/lib/capybara/spec/session/click_link_spec.rb +11 -0
  125. data/lib/capybara/spec/session/evaluate_script_spec.rb +12 -0
  126. data/lib/capybara/spec/session/fill_in_spec.rb +37 -2
  127. data/lib/capybara/spec/session/find_spec.rb +60 -6
  128. data/lib/capybara/spec/session/first_spec.rb +1 -1
  129. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +14 -1
  130. data/lib/capybara/spec/session/frame/within_frame_spec.rb +12 -1
  131. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  132. data/lib/capybara/spec/session/has_button_spec.rb +16 -0
  133. data/lib/capybara/spec/session/has_css_spec.rb +35 -6
  134. data/lib/capybara/spec/session/has_current_path_spec.rb +6 -4
  135. data/lib/capybara/spec/session/has_field_spec.rb +34 -0
  136. data/lib/capybara/spec/session/has_select_spec.rb +32 -4
  137. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  138. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  139. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  140. data/lib/capybara/spec/session/has_text_spec.rb +47 -0
  141. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  142. data/lib/capybara/spec/session/node_spec.rb +574 -16
  143. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  144. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  145. data/lib/capybara/spec/session/scroll_spec.rb +1 -1
  146. data/lib/capybara/spec/session/select_spec.rb +5 -10
  147. data/lib/capybara/spec/session/selectors_spec.rb +24 -3
  148. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  149. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  150. data/lib/capybara/spec/session/window/window_spec.rb +10 -9
  151. data/lib/capybara/spec/spec_helper.rb +7 -2
  152. data/lib/capybara/spec/test_app.rb +26 -21
  153. data/lib/capybara/spec/views/animated.erb +49 -0
  154. data/lib/capybara/spec/views/form.erb +25 -4
  155. data/lib/capybara/spec/views/frame_child.erb +2 -1
  156. data/lib/capybara/spec/views/frame_one.erb +1 -0
  157. data/lib/capybara/spec/views/obscured.erb +9 -9
  158. data/lib/capybara/spec/views/offset.erb +32 -0
  159. data/lib/capybara/spec/views/react.erb +45 -0
  160. data/lib/capybara/spec/views/spatial.erb +31 -0
  161. data/lib/capybara/spec/views/with_animation.erb +29 -1
  162. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  163. data/lib/capybara/spec/views/with_html.erb +28 -2
  164. data/lib/capybara/spec/views/with_js.erb +2 -1
  165. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  166. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  167. data/lib/capybara/version.rb +1 -1
  168. data/lib/capybara/window.rb +10 -10
  169. data/spec/basic_node_spec.rb +6 -6
  170. data/spec/capybara_spec.rb +28 -28
  171. data/spec/dsl_spec.rb +16 -3
  172. data/spec/filter_set_spec.rb +5 -5
  173. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  174. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  175. data/spec/minitest_spec.rb +12 -2
  176. data/spec/minitest_spec_spec.rb +56 -45
  177. data/spec/rack_test_spec.rb +25 -12
  178. data/spec/regexp_dissassembler_spec.rb +53 -39
  179. data/spec/result_spec.rb +50 -54
  180. data/spec/rspec/features_spec.rb +1 -0
  181. data/spec/rspec/shared_spec_matchers.rb +78 -62
  182. data/spec/rspec_spec.rb +5 -5
  183. data/spec/sauce_spec_chrome.rb +1 -0
  184. data/spec/selector_spec.rb +26 -16
  185. data/spec/selenium_spec_chrome.rb +84 -5
  186. data/spec/selenium_spec_chrome_remote.rb +23 -8
  187. data/spec/selenium_spec_edge.rb +23 -8
  188. data/spec/selenium_spec_firefox.rb +16 -21
  189. data/spec/selenium_spec_firefox_remote.rb +4 -13
  190. data/spec/selenium_spec_ie.rb +23 -15
  191. data/spec/selenium_spec_safari.rb +17 -17
  192. data/spec/server_spec.rb +87 -42
  193. data/spec/session_spec.rb +11 -4
  194. data/spec/shared_selenium_node.rb +83 -0
  195. data/spec/shared_selenium_session.rb +62 -72
  196. data/spec/spec_helper.rb +43 -5
  197. metadata +114 -16
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
4
+ xpath do |locator, caption: nil, **|
5
+ xpath = XPath.descendant(:table)
6
+ unless locator.nil?
7
+ locator_matchers = (XPath.attr(:id) == locator.to_s) | XPath.descendant(:caption).is(locator.to_s)
8
+ locator_matchers |= XPath.attr(test_id) == locator if test_id
9
+ xpath = xpath[locator_matchers]
10
+ end
11
+ xpath = xpath[XPath.descendant(:caption) == caption] if caption
12
+ xpath
13
+ end
14
+
15
+ expression_filter(:with_cols, valid_values: [Array]) do |xpath, cols|
16
+ col_conditions = cols.map do |col|
17
+ if col.is_a? Hash
18
+ col.reduce(nil) do |xp, (header, cell_str)|
19
+ header = XPath.descendant(:th)[XPath.string.n.is(header)]
20
+ td = XPath.descendant(:tr)[header].descendant(:td)
21
+ cell_condition = XPath.string.n.is(cell_str)
22
+ if xp
23
+ prev_cell = XPath.ancestor(:table)[1].join(xp)
24
+ cell_condition &= (prev_cell & prev_col_position?(prev_cell))
25
+ end
26
+ td[cell_condition]
27
+ end
28
+ else
29
+ cells_xp = col.reduce(nil) do |prev_cell, cell_str|
30
+ cell_condition = XPath.string.n.is(cell_str)
31
+
32
+ if prev_cell
33
+ prev_cell = XPath.ancestor(:tr)[1].preceding_sibling(:tr).join(prev_cell)
34
+ cell_condition &= (prev_cell & prev_col_position?(prev_cell))
35
+ end
36
+
37
+ XPath.descendant(:td)[cell_condition]
38
+ end
39
+ XPath.descendant(:tr).join(cells_xp)
40
+ end
41
+ end.reduce(:&)
42
+ xpath[col_conditions]
43
+ end
44
+
45
+ expression_filter(:cols, valid_values: [Array]) do |xpath, cols|
46
+ raise ArgumentError, ':cols must be an Array of Arrays' unless cols.all?(Array)
47
+
48
+ rows = cols.transpose
49
+ col_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
50
+ xpath[match_row_count(rows.size)][col_conditions]
51
+ end
52
+
53
+ expression_filter(:with_rows, valid_values: [Array]) do |xpath, rows|
54
+ rows_conditions = rows.map { |row| match_row(row) }.reduce(:&)
55
+ xpath[rows_conditions]
56
+ end
57
+
58
+ expression_filter(:rows, valid_values: [Array]) do |xpath, rows|
59
+ rows_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
60
+ xpath[match_row_count(rows.size)][rows_conditions]
61
+ end
62
+
63
+ describe_expression_filters do |caption: nil, **|
64
+ " with caption \"#{caption}\"" if caption
65
+ end
66
+
67
+ def prev_col_position?(cell)
68
+ XPath.position.equals(cell_position(cell))
69
+ end
70
+
71
+ def cell_position(cell)
72
+ cell.preceding_sibling(:td).count.plus(1)
73
+ end
74
+
75
+ def match_row(row, match_size: false)
76
+ xp = XPath.descendant(:tr)[
77
+ if row.is_a? Hash
78
+ row_match_cells_to_headers(row)
79
+ else
80
+ XPath.descendant(:td)[row_match_ordered_cells(row)]
81
+ end
82
+ ]
83
+ xp = xp[XPath.descendant(:td).count.equals(row.size)] if match_size
84
+ xp
85
+ end
86
+
87
+ def match_row_count(size)
88
+ XPath.descendant(:tbody).descendant(:tr).count.equals(size) |
89
+ (XPath.descendant(:tr).count.equals(size) & ~XPath.descendant(:tbody))
90
+ end
91
+
92
+ def row_match_cells_to_headers(row)
93
+ row.map do |header, cell|
94
+ header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)]
95
+ XPath.descendant(:td)[
96
+ XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
97
+ ]
98
+ end.reduce(:&)
99
+ end
100
+
101
+ def row_match_ordered_cells(row)
102
+ row_conditions = row.map do |cell|
103
+ XPath.self(:td)[XPath.string.n.is(cell)]
104
+ end
105
+ row_conditions.reverse.reduce do |cond, cell|
106
+ cell[XPath.following_sibling[cond]]
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:table_row, locator_type: [Array, Hash]) do
4
+ xpath do |locator|
5
+ xpath = XPath.descendant(:tr)
6
+ if locator.is_a? Hash
7
+ locator.reduce(xpath) do |xp, (header, cell)|
8
+ header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)]
9
+ cell_xp = XPath.descendant(:td)[
10
+ XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
11
+ ]
12
+ xp[cell_xp]
13
+ end
14
+ else
15
+ initial_td = XPath.descendant(:td)[XPath.string.n.is(locator.shift)]
16
+ tds = locator.reverse.map { |cell| XPath.following_sibling(:td)[XPath.string.n.is(cell)] }
17
+ .reduce { |xp, cell| xp[cell] }
18
+ xpath[initial_td[tds]]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:xpath, locator_type: [:to_xpath, String], raw_locator: true) do
4
+ xpath { |xpath| xpath }
5
+ end
@@ -15,13 +15,15 @@ module Capybara
15
15
  instance_eval(&block)
16
16
  end
17
17
 
18
- def node_filter(name, *types_and_options, &block)
19
- add_filter(name, Filters::NodeFilter, *types_and_options, &block)
18
+ def node_filter(names, *types, **options, &block)
19
+ Array(names).each do |name|
20
+ add_filter(name, Filters::NodeFilter, *types, **options, &block)
21
+ end
20
22
  end
21
23
  alias_method :filter, :node_filter
22
24
 
23
- def expression_filter(name, *types_and_options, &block)
24
- add_filter(name, Filters::ExpressionFilter, *types_and_options, &block)
25
+ def expression_filter(name, *types, **options, &block)
26
+ add_filter(name, Filters::ExpressionFilter, *types, **options, &block)
25
27
  end
26
28
 
27
29
  def describe(what = nil, &block)
@@ -40,9 +42,9 @@ module Capybara
40
42
  def description(node_filters: true, expression_filters: true, **options)
41
43
  opts = options_with_defaults(options)
42
44
  description = +''
43
- description << undeclared_descriptions.map { |desc| desc.call(opts).to_s }.join
44
- description << expression_filter_descriptions.map { |desc| desc.call(opts).to_s }.join if expression_filters
45
- description << node_filter_descriptions.map { |desc| desc.call(opts).to_s }.join if node_filters
45
+ description << undeclared_descriptions.map { |desc| desc.call(**opts).to_s }.join
46
+ description << expression_filter_descriptions.map { |desc| desc.call(**opts).to_s }.join if expression_filters
47
+ description << node_filter_descriptions.map { |desc| desc.call(**opts).to_s }.join if node_filters
46
48
  description
47
49
  end
48
50
 
@@ -110,9 +112,11 @@ module Capybara
110
112
 
111
113
  def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
112
114
  types.each { |type| options[type] = true }
113
- raise 'ArgumentError', ':default option is not supported for filters with a :matcher option' if matcher && options[:default]
115
+ if matcher && options[:default]
116
+ raise 'ArgumentError', ':default option is not supported for filters with a :matcher option'
117
+ end
114
118
 
115
- filter = filter_class.new(name, matcher, block, options)
119
+ filter = filter_class.new(name, matcher, block, **options)
116
120
  (filter_class <= Filters::ExpressionFilter ? @expression_filters : @node_filters)[name] = filter
117
121
  end
118
122
  end
@@ -24,6 +24,10 @@ module Capybara
24
24
  @options.key?(:skip_if) && value == @options[:skip_if]
25
25
  end
26
26
 
27
+ def format
28
+ @options[:format]
29
+ end
30
+
27
31
  def matcher?
28
32
  !@matcher.nil?
29
33
  end
@@ -34,7 +38,7 @@ module Capybara
34
38
 
35
39
  def handles_option?(option_name)
36
40
  if matcher?
37
- option_name =~ @matcher
41
+ @matcher.match? option_name
38
42
  else
39
43
  @name == option_name
40
44
  end
@@ -44,7 +48,12 @@ module Capybara
44
48
 
45
49
  def apply(subject, name, value, skip_value, ctx)
46
50
  return skip_value if skip?(value)
47
- raise ArgumentError, "Invalid value #{value.inspect} passed to #{self.class.name.split('::').last} #{name}#{" : #{@name}" if @name.is_a?(Regexp)}" unless valid_value?(value)
51
+
52
+ unless valid_value?(value)
53
+ raise ArgumentError,
54
+ "Invalid value #{value.inspect} passed to #{self.class.name.split('::').last} #{name}" \
55
+ "#{" : #{name}" if @name.is_a?(Regexp)}"
56
+ end
48
57
 
49
58
  if @block.arity == 2
50
59
  filter_context(ctx).instance_exec(subject, value, &@block)
@@ -7,11 +7,21 @@ module Capybara
7
7
  module Filters
8
8
  class LocatorFilter < NodeFilter
9
9
  def initialize(block, **options)
10
- super(nil, nil, block, options)
10
+ super(nil, nil, block, **options)
11
11
  end
12
12
 
13
- def matches?(node, value, context = nil)
14
- super(node, nil, value, context)
13
+ def matches?(node, value, context = nil, exact:)
14
+ apply(node, value, true, context, exact: exact, format: context&.default_format)
15
+ rescue Capybara::ElementNotFound
16
+ false
17
+ end
18
+
19
+ private
20
+
21
+ def apply(subject, value, skip_value, ctx, **options)
22
+ return skip_value if skip?(value)
23
+
24
+ filter_context(ctx).instance_exec(subject, value, **options, &@block)
15
25
  end
16
26
  end
17
27
  end
@@ -100,6 +100,8 @@ module Capybara
100
100
  def extract_strings(process_alternatives)
101
101
  strings = []
102
102
  each do |exp|
103
+ next if exp.ignore?
104
+
103
105
  next strings.push(nil) if exp.optional? && !process_alternatives
104
106
 
105
107
  next strings.push(exp.alternative_strings) if exp.alternation? && process_alternatives
@@ -159,6 +161,11 @@ module Capybara
159
161
  alts.all?(&:any?) ? Set.new(alts) : nil
160
162
  end
161
163
 
164
+ def ignore?
165
+ [Regexp::Expression::Assertion::NegativeLookahead,
166
+ Regexp::Expression::Assertion::NegativeLookbehind].any? { |klass| @exp.is_a? klass }
167
+ end
168
+
162
169
  private
163
170
 
164
171
  def indeterminate?
@@ -166,11 +173,11 @@ module Capybara
166
173
  end
167
174
 
168
175
  def min_repeat
169
- @exp.quantifier&.min || 1
176
+ @exp.repetitions.begin
170
177
  end
171
178
 
172
179
  def max_repeat
173
- @exp.quantifier&.max || 1
180
+ @exp.repetitions.end
174
181
  end
175
182
 
176
183
  def fixed_repeat?
@@ -1,190 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Style/AsciiComments
4
-
5
- require 'capybara/selector/filter_set'
6
- require 'capybara/selector/css'
7
- require 'capybara/selector/regexp_disassembler'
8
- require 'capybara/selector/builders/xpath_builder'
9
- require 'capybara/selector/builders/css_builder'
10
-
11
3
  module Capybara
12
- #
13
- # ## Built-in Selectors
14
- #
15
- # * **:xpath** - Select elements by XPath expression
16
- # * Locator: An XPath expression
17
- #
18
- # * **:css** - Select elements by CSS selector
19
- # * Locator: A CSS selector
20
- #
21
- # * **:id** - Select element by id
22
- # * Locator: (String, Regexp, XPath::Expression) The id of the element to match
23
- #
24
- # * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
25
- # * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
26
- # * Filters:
27
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
28
- # * :name (String) — Matches the name attribute
29
- # * :placeholder (String) — Matches the placeholder attribute
30
- # * :type (String) — Matches the type attribute of the field or element type for 'textarea' and 'select'
31
- # * :readonly (Boolean)
32
- # * :with (String) — Matches the current value of the field
33
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
34
- # * :checked (Boolean) — Match checked fields?
35
- # * :unchecked (Boolean) — Match unchecked fields?
36
- # * :disabled (Boolean) — Match disabled field?
37
- # * :multiple (Boolean) — Match fields that accept multiple values
38
- # * :style (String, Regexp, Hash)
39
- #
40
- # * **:fieldset** - Select fieldset elements
41
- # * Locator: Matches id or contents of wrapped legend
42
- # * Filters:
43
- # * :id (String, Regexp, XPath::Expression) — Matches id attribute
44
- # * :legend (String) — Matches contents of wrapped legend
45
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
46
- # * :style (String, Regexp, Hash)
47
- #
48
- # * **:link** - Find links ( <a> elements with an href attribute )
49
- # * Locator: Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
50
- # * Filters:
51
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
52
- # * :title (String) — Matches the title attribute
53
- # * :alt (String) — Matches the alt attribute of a contained img element
54
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
55
- # * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
56
- # * :style (String, Regexp, Hash)
57
- #
58
- # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
59
- # * Locator: Matches the id, Capybara.test_id attribute, 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
60
- # * Filters:
61
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
62
- # * :title (String) — Matches the title attribute
63
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
64
- # * :value (String) — Matches the value of an input button
65
- # * :type
66
- # * :style (String, Regexp, Hash)
67
- #
68
- # * **:link_or_button** - Find links or buttons
69
- # * Locator: See :link and :button selectors
70
- #
71
- # * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
72
- # * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
73
- # * Filters:
74
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
75
- # * :name (String) — Matches the name attribute
76
- # * :placeholder (String) — Matches the placeholder attribute
77
- # * :with (String) — Matches the current value of the field
78
- # * :type (String) — Matches the type attribute of the field or element type for 'textarea'
79
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
80
- # * :disabled (Boolean) — Match disabled field?
81
- # * :multiple (Boolean) — Match fields that accept multiple values
82
- # * :style (String, Regexp, Hash)
83
- #
84
- # * **:radio_button** - Find radio buttons
85
- # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
86
- # * Filters:
87
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
88
- # * :name (String) — Matches the name attribute
89
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
90
- # * :checked (Boolean) — Match checked fields?
91
- # * :unchecked (Boolean) — Match unchecked fields?
92
- # * :disabled (Boolean) — Match disabled field?
93
- # * :option (String) — Match the value
94
- # * :style (String, Regexp, Hash)
95
- #
96
- # * **:checkbox** - Find checkboxes
97
- # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
98
- # * Filters:
99
- # * *:id (String, Regexp, XPath::Expression) — Matches the id attribute
100
- # * *:name (String) — Matches the name attribute
101
- # * *:class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
102
- # * *:checked (Boolean) — Match checked fields?
103
- # * *:unchecked (Boolean) — Match unchecked fields?
104
- # * *:disabled (Boolean) — Match disabled field?
105
- # * *:option (String) — Match the value
106
- # * :style (String, Regexp, Hash)
107
- #
108
- # * **:select** - Find select elements
109
- # * Locator: Match id, Capybara.test_id attribute, name, placeholder, or associated label text
110
- # * Filters:
111
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
112
- # * :name (String) — Matches the name attribute
113
- # * :placeholder (String) — Matches the placeholder attribute
114
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
115
- # * :disabled (Boolean) — Match disabled field?
116
- # * :multiple (Boolean) — Match fields that accept multiple values
117
- # * :options (Array<String>) — Exact match options
118
- # * :with_options (Array<String>) — Partial match options
119
- # * :selected (String, Array<String>) — Match the selection(s)
120
- # * :with_selected (String, Array<String>) — Partial match the selection(s)
121
- # * :style (String, Regexp, Hash)
122
- #
123
- # * **:option** - Find option elements
124
- # * Locator: Match text of option
125
- # * Filters:
126
- # * :disabled (Boolean) — Match disabled option
127
- # * :selected (Boolean) — Match selected option
128
- #
129
- # * **:datalist_input**
130
- # * Locator:
131
- # * Filters:
132
- # * :disabled
133
- # * :name
134
- # * :placeholder
135
- #
136
- # * **:datalist_option**
137
- # * Locator:
138
- #
139
- # * **:file_field** - Find file input elements
140
- # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
141
- # * Filters:
142
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
143
- # * :name (String) — Matches the name attribute
144
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
145
- # * :disabled (Boolean) — Match disabled field?
146
- # * :multiple (Boolean) — Match field that accepts multiple values
147
- # * :style (String, Regexp, Hash)
148
- #
149
- # * **:label** - Find label elements
150
- # * Locator: Match id or text contents
151
- # * Filters:
152
- # * :for (Element, String) — The element or id of the element associated with the label
153
- #
154
- # * **:table** - Find table elements
155
- # * Locator: id or caption text of table
156
- # * Filters:
157
- # * :id (String, Regexp, XPath::Expression) — Match id attribute of table
158
- # * :caption (String) — Match text of associated caption
159
- # * :class ((String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
160
- # * :style (String, Regexp, Hash)
161
- # * :with_rows (Array<Array<String>>, Array<Hash<String, String>>) - Partial match <td> data - visibility of <td> elements is not considered
162
- # * :rows (Array<Array<String>>) — Match all <td>s - visibility of <td> elements is not considered
163
- # * :with_cols (Array<Array<String>>, Array<Hash<String, String>>) - Partial match <td> data - visibility of <td> elements is not considered
164
- # * :cols (Array<Array<String>>) — Match all <td>s - visibility of <td> elements is not considered
165
- #
166
- # * **:table_row** - Find table row
167
- # * Locator: Array<String>, Hash<String,String> table row <td> contents - visibility of <td> elements is not considered
168
- #
169
- # * **:frame** - Find frame/iframe elements
170
- # * Locator: Match id or name
171
- # * Filters:
172
- # * :id (String, Regexp, XPath::Expression) — Match id attribute
173
- # * :name (String) — Match name attribute
174
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
175
- # * :style (String, Regexp, Hash)
176
- #
177
- # * **:element**
178
- # * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
179
- # * Filters: Matches on any element attribute
180
- #
181
- class Selector
182
- attr_reader :name, :format
183
- extend Forwardable
184
-
4
+ class Selector < SimpleDelegator
185
5
  class << self
186
6
  def all
187
- @selectors ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
7
+ @definitions ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
188
8
  end
189
9
 
190
10
  def [](name)
@@ -192,7 +12,7 @@ module Capybara
192
12
  end
193
13
 
194
14
  def add(name, **options, &block)
195
- all[name.to_sym] = Capybara::Selector.new(name.to_sym, **options, &block)
15
+ all[name.to_sym] = Definition.new(name.to_sym, **options, &block)
196
16
  end
197
17
 
198
18
  def update(name, &block)
@@ -208,226 +28,63 @@ module Capybara
208
28
  end
209
29
  end
210
30
 
211
- def initialize(name, locator_type: nil, raw_locator: false, &block)
212
- @name = name
213
- @filter_set = FilterSet.add(name) {}
214
- @match = nil
215
- @label = nil
216
- @failure_message = nil
217
- @format = nil
218
- @expression = nil
219
- @expression_filters = {}
220
- @locator_filter = nil
221
- @default_visibility = nil
222
- @locator_type = locator_type
223
- @raw_locator = raw_locator
224
- @config = {
225
- enable_aria_label: false,
226
- test_id: nil
227
- }
228
- instance_eval(&block)
229
- end
230
-
231
- def custom_filters
232
- warn "Deprecated: Selector#custom_filters is not valid when same named expression and node filter exist - don't use"
233
- node_filters.merge(expression_filters).freeze
234
- end
31
+ attr_reader :errors
235
32
 
236
- def node_filters
237
- @filter_set.node_filters
33
+ def initialize(definition, config:, format:)
34
+ definition = self.class[definition] unless definition.is_a? Definition
35
+ super(definition)
36
+ @definition = definition
37
+ @config = config
38
+ @format = format
39
+ @errors = []
238
40
  end
239
41
 
240
- def expression_filters
241
- @filter_set.expression_filters
42
+ def format
43
+ @format || @definition.default_format
242
44
  end
45
+ alias_method :current_format, :format
243
46
 
244
- ##
245
- #
246
- # Define a selector by an xpath expression
247
- #
248
- # @overload xpath(*expression_filters, &block)
249
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that are implemented via this expression, if not specified the names of any keyword parameters in the block will be used
250
- # @yield [locator, options] The block to use to generate the XPath expression
251
- # @yieldparam [String] locator The locator string passed to the query
252
- # @yieldparam [Hash] options The options hash passed to the query
253
- # @yieldreturn [#to_xpath, #to_s] An object that can produce an xpath expression
254
- #
255
- # @overload xpath()
256
- # @return [#call] The block that will be called to generate the XPath expression
257
- #
258
- def xpath(*allowed_filters, &block)
259
- expression(:xpath, allowed_filters, &block)
260
- end
261
-
262
- ##
263
- #
264
- # Define a selector by a CSS selector
265
- #
266
- # @overload css(*expression_filters, &block)
267
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this CSS selector
268
- # @yield [locator, options] The block to use to generate the CSS selector
269
- # @yieldparam [String] locator The locator string passed to the query
270
- # @yieldparam [Hash] options The options hash passed to the query
271
- # @yieldreturn [#to_s] An object that can produce a CSS selector
272
- #
273
- # @overload css()
274
- # @return [#call] The block that will be called to generate the CSS selector
275
- #
276
- def css(*allowed_filters, &block)
277
- expression(:css, allowed_filters, &block)
47
+ def enable_aria_label
48
+ @config[:enable_aria_label]
278
49
  end
279
50
 
280
- ##
281
- #
282
- # Automatic selector detection
283
- #
284
- # @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector
285
- # @yieldparam [String], locator The locator string used to determin if it matches the selector
286
- # @yieldreturn [Boolean] Whether this selector matches the locator string
287
- # @return [#call] The block that will be used to detect selector match
288
- #
289
- def match(&block)
290
- @match = block if block
291
- @match
51
+ def enable_aria_role
52
+ @config[:enable_aria_role]
292
53
  end
293
54
 
294
- ##
295
- #
296
- # Set/get a descriptive label for the selector
297
- #
298
- # @overload label(label)
299
- # @param [String] label A descriptive label for this selector - used in error messages
300
- # @overload label()
301
- # @return [String] The currently set label
302
- #
303
- def label(label = nil)
304
- @label = label if label
305
- @label
55
+ def test_id
56
+ @config[:test_id]
306
57
  end
307
58
 
308
- ##
309
- #
310
- # Description of the selector
311
- #
312
- # @!method description(options)
313
- # @param [Hash] options The options of the query used to generate the description
314
- # @return [String] Description of the selector when used with the options passed
315
- def_delegator :@filter_set, :description
316
-
317
- def call(locator, selector_config: {}, **options)
318
- @config.merge! selector_config
59
+ def call(locator, **options)
319
60
  if format
320
- @expression.call(locator, options)
61
+ raise ArgumentError, "Selector #{@name} does not support #{format}" unless expressions.key?(format)
62
+
63
+ instance_exec(locator, **options, &expressions[format])
321
64
  else
322
65
  warn 'Selector has no format'
323
66
  end
324
67
  ensure
325
- warn "Locator #{locator.inspect} must #{locator_description}. This will raise an error in a future version of Capybara." unless locator_valid?(locator)
326
- end
327
-
328
- ##
329
- #
330
- # Should this selector be used for the passed in locator
331
- #
332
- # This is used by the automatic selector selection mechanism when no selector type is passed to a selector query
333
- #
334
- # @param [String] locator The locator passed to the query
335
- # @return [Boolean] Whether or not to use this selector
336
- #
337
- def match?(locator)
338
- @match&.call(locator)
339
- end
340
-
341
- ##
342
- #
343
- # Define a node filter for use with this selector
344
- #
345
- # @!method node_filter(name, *types, options={}, &block)
346
- # @param [Symbol, Regexp] name The filter name
347
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
348
- # @param [Hash] options ({}) Options of the filter
349
- # @option options [Array<>] :valid_values Valid values for this filter
350
- # @option options :default The default value of the filter (if any)
351
- # @option options :skip_if Value of the filter that will cause it to be skipped
352
- # @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.
353
- #
354
- # If a Symbol is passed for the name the block should accept | node, option_value |, while if a Regexp
355
- # is passed for the name the block should accept | node, option_name, option_value |. In either case
356
- # the block should return `true` if the node passes the filer or `false` if it doesn't
357
-
358
- # @!method filter
359
- # See {Selector#node_filter}
360
-
361
- ##
362
- #
363
- # Define an expression filter for use with this selector
364
- #
365
- # @!method expression_filter(name, *types, matcher: nil, **options, &block)
366
- # @param [Symbol, Regexp] name The filter name
367
- # @param [Regexp] matcher (nil) A Regexp used to check whether a specific option is handled by this filter
368
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
369
- # @param [Hash] options ({}) Options of the filter
370
- # @option options [Array<>] :valid_values Valid values for this filter
371
- # @option options :default The default value of the filter (if any)
372
- # @option options :skip_if Value of the filter that will cause it to be skipped
373
- # @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.
374
- #
375
- # If a Symbol is passed for the name the block should accept | current_expression, option_value |, while if a Regexp
376
- # is passed for the name the block should accept | current_expression, option_name, option_value |. In either case
377
- # the block should return the modified expression
378
-
379
- def_delegators :@filter_set, :node_filter, :expression_filter, :filter
380
-
381
- def locator_filter(*types, **options, &block)
382
- types.each { |type| options[type] = true }
383
- @locator_filter = Filters::LocatorFilter.new(block, options) if block
384
- @locator_filter
385
- end
386
-
387
- def filter_set(name, filters_to_use = nil)
388
- @filter_set.import(name, filters_to_use)
389
- end
390
-
391
- def_delegator :@filter_set, :describe
392
-
393
- def describe_expression_filters(&block)
394
- if block_given?
395
- describe(:expression_filters, &block)
396
- else
397
- describe(:expression_filters) do |**options|
398
- describe_all_expression_filters(options)
399
- end
68
+ unless locator_valid?(locator)
69
+ warn "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. This will raise an error in a future version of Capybara."
400
70
  end
401
71
  end
402
72
 
403
- def describe_node_filters(&block)
404
- describe(:node_filters, &block)
73
+ def add_error(error_msg)
74
+ errors << error_msg
405
75
  end
406
76
 
407
- ##
408
- #
409
- # Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
410
- # If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements
411
- #
412
- # @param [Symbol] default_visibility Only find elements with the specified visibility:
413
- # * :all - finds visible and invisible elements.
414
- # * :hidden - only finds invisible elements.
415
- # * :visible - only finds visible elements.
416
- def visible(default_visibility = nil, &block)
417
- @default_visibility = block || default_visibility
77
+ def expression_for(name, locator, config: @config, format: current_format, **options)
78
+ Selector.new(name, config: config, format: format).call(locator, **options)
418
79
  end
419
80
 
420
- def default_visibility(fallback = Capybara.ignore_hidden_elements, options = {})
421
- vis = if @default_visibility&.respond_to?(:call)
422
- @default_visibility.call(options)
423
- else
424
- @default_visibility
425
- end
426
- vis.nil? ? fallback : vis
427
- end
428
-
429
- def add_error(error_msg)
430
- errors << error_msg
81
+ # @api private
82
+ def with_filter_errors(errors)
83
+ old_errors = @errors
84
+ @errors = errors
85
+ yield
86
+ ensure
87
+ @errors = old_errors
431
88
  end
432
89
 
433
90
  # @api private
@@ -438,39 +95,12 @@ module Capybara
438
95
  when :xpath
439
96
  Capybara::Selector::XPathBuilder
440
97
  else
441
- raise NotImplementedError, "No builder exists for selector of type #{format}"
98
+ raise NotImplementedError, "No builder exists for selector of type #{default_format}"
442
99
  end.new(expr)
443
100
  end
444
101
 
445
- # @api private
446
- def with_filter_errors(errors)
447
- Thread.current["capybara_#{object_id}_errors"] = errors
448
- yield
449
- ensure
450
- Thread.current["capybara_#{object_id}_errors"] = nil
451
- end
452
-
453
- # @api private
454
- def raw_locator?
455
- !!@raw_locator
456
- end
457
-
458
102
  private
459
103
 
460
- def locator_types
461
- return nil unless @locator_type
462
-
463
- Array(@locator_type)
464
- end
465
-
466
- def locator_valid?(locator)
467
- return true unless locator && locator_types
468
-
469
- locator_types&.any? do |type_or_method|
470
- type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
471
- end
472
- end
473
-
474
104
  def locator_description
475
105
  locator_types.group_by { |lt| lt.is_a? Symbol }.map do |symbol, types_or_methods|
476
106
  if symbol
@@ -481,16 +111,12 @@ module Capybara
481
111
  end.join(' or ')
482
112
  end
483
113
 
484
- def errors
485
- Thread.current["capybara_#{object_id}_errors"] || []
486
- end
487
-
488
- def enable_aria_label
489
- @config[:enable_aria_label]
490
- end
114
+ def locator_valid?(locator)
115
+ return true unless locator && locator_types
491
116
 
492
- def test_id
493
- @config[:test_id]
117
+ locator_types&.any? do |type_or_method|
118
+ type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
119
+ end
494
120
  end
495
121
 
496
122
  def locate_field(xpath, locator, **_options)
@@ -509,22 +135,6 @@ module Capybara
509
135
  locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
510
136
  end
511
137
 
512
- def describe_all_expression_filters(**opts)
513
- expression_filters.map do |ef_name, ef|
514
- if ef.matcher?
515
- handled_custom_keys(ef, opts.keys).map { |key| " with #{ef_name}[#{key} => #{opts[key]}]" }.join
516
- elsif opts.key?(ef_name)
517
- " with #{ef_name} #{opts[ef_name]}"
518
- end
519
- end.join
520
- end
521
-
522
- def handled_custom_keys(filter, keys)
523
- keys.select do |key|
524
- filter.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key)
525
- end
526
- end
527
-
528
138
  def find_by_attr(attribute, value)
529
139
  finder_name = "find_by_#{attribute}_attr"
530
140
  if respond_to?(finder_name, true)
@@ -537,20 +147,5 @@ module Capybara
537
147
  def find_by_class_attr(classes)
538
148
  Array(classes).map { |klass| XPath.attr(:class).contains_word(klass) }.reduce(:&)
539
149
  end
540
-
541
- def parameter_names(block)
542
- block.parameters.select { |(type, _name)| %i[key keyreq].include? type }.map { |(_type, name)| name }
543
- end
544
-
545
- def expression(type, allowed_filters, &block)
546
- if block
547
- @format, @expression = type, block
548
- allowed_filters = parameter_names(block) if allowed_filters.empty?
549
- allowed_filters.flatten.each { |ef| expression_filters[ef] = Filters::IdentityExpressionFilter.new(ef) }
550
- end
551
- format == type ? @expression : nil
552
- end
553
150
  end
554
151
  end
555
-
556
- # rubocop:enable Style/AsciiComments