capybara 3.0.0 → 3.36.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (308) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/History.md +777 -10
  4. data/License.txt +1 -1
  5. data/README.md +75 -58
  6. data/lib/capybara/config.rb +29 -10
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +22 -4
  9. data/lib/capybara/driver/node.rb +34 -9
  10. data/lib/capybara/dsl.rb +14 -6
  11. data/lib/capybara/helpers.rb +52 -7
  12. data/lib/capybara/minitest/spec.rb +173 -84
  13. data/lib/capybara/minitest.rb +250 -144
  14. data/lib/capybara/node/actions.rb +248 -124
  15. data/lib/capybara/node/base.rb +34 -20
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +13 -15
  18. data/lib/capybara/node/element.rb +339 -113
  19. data/lib/capybara/node/finders.rb +95 -82
  20. data/lib/capybara/node/matchers.rb +338 -157
  21. data/lib/capybara/node/simple.rb +54 -15
  22. data/lib/capybara/queries/active_element_query.rb +18 -0
  23. data/lib/capybara/queries/ancestor_query.rb +9 -10
  24. data/lib/capybara/queries/base_query.rb +25 -18
  25. data/lib/capybara/queries/current_path_query.rb +16 -6
  26. data/lib/capybara/queries/match_query.rb +11 -0
  27. data/lib/capybara/queries/selector_query.rb +600 -103
  28. data/lib/capybara/queries/sibling_query.rb +9 -7
  29. data/lib/capybara/queries/style_query.rb +45 -0
  30. data/lib/capybara/queries/text_query.rb +40 -22
  31. data/lib/capybara/queries/title_query.rb +2 -2
  32. data/lib/capybara/rack_test/browser.rb +47 -28
  33. data/lib/capybara/rack_test/driver.rb +12 -3
  34. data/lib/capybara/rack_test/errors.rb +6 -0
  35. data/lib/capybara/rack_test/form.rb +53 -50
  36. data/lib/capybara/rack_test/node.rb +114 -37
  37. data/lib/capybara/rails.rb +1 -1
  38. data/lib/capybara/registration_container.rb +44 -0
  39. data/lib/capybara/registrations/drivers.rb +42 -0
  40. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  41. data/lib/capybara/registrations/servers.rb +45 -0
  42. data/lib/capybara/result.rb +86 -52
  43. data/lib/capybara/rspec/features.rb +8 -10
  44. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  45. data/lib/capybara/rspec/matchers/base.rb +111 -0
  46. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  47. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  48. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  49. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  50. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  51. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  52. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  53. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  54. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  55. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  56. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  57. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  58. data/lib/capybara/rspec/matchers.rb +138 -316
  59. data/lib/capybara/rspec.rb +3 -2
  60. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  61. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  62. data/lib/capybara/selector/css.rb +89 -12
  63. data/lib/capybara/selector/definition/button.rb +68 -0
  64. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  65. data/lib/capybara/selector/definition/css.rb +10 -0
  66. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  67. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  68. data/lib/capybara/selector/definition/element.rb +28 -0
  69. data/lib/capybara/selector/definition/field.rb +40 -0
  70. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  71. data/lib/capybara/selector/definition/file_field.rb +13 -0
  72. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  73. data/lib/capybara/selector/definition/frame.rb +17 -0
  74. data/lib/capybara/selector/definition/id.rb +6 -0
  75. data/lib/capybara/selector/definition/label.rb +62 -0
  76. data/lib/capybara/selector/definition/link.rb +54 -0
  77. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  78. data/lib/capybara/selector/definition/option.rb +27 -0
  79. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  80. data/lib/capybara/selector/definition/select.rb +81 -0
  81. data/lib/capybara/selector/definition/table.rb +109 -0
  82. data/lib/capybara/selector/definition/table_row.rb +21 -0
  83. data/lib/capybara/selector/definition/xpath.rb +5 -0
  84. data/lib/capybara/selector/definition.rb +279 -0
  85. data/lib/capybara/selector/filter.rb +1 -0
  86. data/lib/capybara/selector/filter_set.rb +72 -27
  87. data/lib/capybara/selector/filters/base.rb +45 -2
  88. data/lib/capybara/selector/filters/expression_filter.rb +5 -6
  89. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  90. data/lib/capybara/selector/filters/node_filter.rb +18 -4
  91. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  92. data/lib/capybara/selector/selector.rb +85 -200
  93. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  94. data/lib/capybara/selector.rb +226 -537
  95. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  96. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  98. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  99. data/lib/capybara/selenium/driver.rb +296 -238
  100. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  101. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  102. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  103. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  104. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  105. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  106. data/lib/capybara/selenium/extensions/find.rb +110 -0
  107. data/lib/capybara/selenium/extensions/html5_drag.rb +228 -0
  108. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  109. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  110. data/lib/capybara/selenium/logger_suppressor.rb +40 -0
  111. data/lib/capybara/selenium/node.rb +436 -166
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +137 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  118. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  119. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  120. data/lib/capybara/selenium/patches/logs.rb +45 -0
  121. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  122. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  123. data/lib/capybara/server/animation_disabler.rb +69 -0
  124. data/lib/capybara/server/checker.rb +44 -0
  125. data/lib/capybara/server/middleware.rb +71 -0
  126. data/lib/capybara/server.rb +58 -67
  127. data/lib/capybara/session/config.rb +38 -6
  128. data/lib/capybara/session/matchers.rb +26 -19
  129. data/lib/capybara/session.rb +258 -190
  130. data/lib/capybara/spec/public/jquery.js +5 -5
  131. data/lib/capybara/spec/public/offset.js +6 -0
  132. data/lib/capybara/spec/public/test.js +122 -8
  133. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  134. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  135. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  136. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  137. data/lib/capybara/spec/session/all_spec.rb +135 -42
  138. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  139. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  140. data/lib/capybara/spec/session/{assert_current_path.rb → assert_current_path_spec.rb} +20 -18
  141. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  142. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  143. data/lib/capybara/spec/session/{assert_text.rb → assert_text_spec.rb} +62 -38
  144. data/lib/capybara/spec/session/{assert_title.rb → assert_title_spec.rb} +12 -12
  145. data/lib/capybara/spec/session/attach_file_spec.rb +120 -72
  146. data/lib/capybara/spec/session/body_spec.rb +11 -13
  147. data/lib/capybara/spec/session/check_spec.rb +111 -51
  148. data/lib/capybara/spec/session/choose_spec.rb +62 -30
  149. data/lib/capybara/spec/session/click_button_spec.rb +227 -161
  150. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -30
  151. data/lib/capybara/spec/session/click_link_spec.rb +78 -55
  152. data/lib/capybara/spec/session/current_scope_spec.rb +7 -7
  153. data/lib/capybara/spec/session/current_url_spec.rb +44 -37
  154. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  155. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  156. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +11 -9
  157. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  158. data/lib/capybara/spec/session/element/match_xpath_spec.rb +8 -6
  159. data/lib/capybara/spec/session/element/matches_selector_spec.rb +70 -56
  160. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  161. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  162. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  163. data/lib/capybara/spec/session/fill_in_spec.rb +104 -44
  164. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  165. data/lib/capybara/spec/session/find_by_id_spec.rb +8 -8
  166. data/lib/capybara/spec/session/find_field_spec.rb +33 -31
  167. data/lib/capybara/spec/session/find_link_spec.rb +32 -14
  168. data/lib/capybara/spec/session/find_spec.rb +236 -141
  169. data/lib/capybara/spec/session/first_spec.rb +44 -43
  170. data/lib/capybara/spec/session/frame/frame_title_spec.rb +6 -6
  171. data/lib/capybara/spec/session/frame/frame_url_spec.rb +6 -6
  172. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +32 -20
  173. data/lib/capybara/spec/session/frame/within_frame_spec.rb +44 -19
  174. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  175. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  176. data/lib/capybara/spec/session/has_all_selectors_spec.rb +18 -18
  177. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  178. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  179. data/lib/capybara/spec/session/has_button_spec.rb +92 -12
  180. data/lib/capybara/spec/session/has_css_spec.rb +271 -137
  181. data/lib/capybara/spec/session/has_current_path_spec.rb +48 -33
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +30 -6
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +26 -24
  185. data/lib/capybara/spec/session/has_select_spec.rb +75 -47
  186. data/lib/capybara/spec/session/has_selector_spec.rb +102 -69
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +170 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +108 -52
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  195. data/lib/capybara/spec/session/node_spec.rb +871 -122
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +15 -12
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +52 -37
  199. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +5 -4
  202. data/lib/capybara/spec/session/save_page_spec.rb +41 -38
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +13 -11
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  206. data/lib/capybara/spec/session/select_spec.rb +102 -76
  207. data/lib/capybara/spec/session/selectors_spec.rb +51 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +26 -24
  210. data/lib/capybara/spec/session/title_spec.rb +8 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +40 -21
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +59 -53
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +22 -19
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +4 -3
  216. data/lib/capybara/spec/session/window/open_new_window_spec.rb +4 -3
  217. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +25 -21
  218. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +10 -5
  219. data/lib/capybara/spec/session/window/window_spec.rb +88 -54
  220. data/lib/capybara/spec/session/window/windows_spec.rb +10 -7
  221. data/lib/capybara/spec/session/window/within_window_spec.rb +17 -16
  222. data/lib/capybara/spec/session/within_spec.rb +69 -44
  223. data/lib/capybara/spec/spec_helper.rb +41 -53
  224. data/lib/capybara/spec/test_app.rb +79 -40
  225. data/lib/capybara/spec/views/animated.erb +49 -0
  226. data/lib/capybara/spec/views/form.erb +134 -42
  227. data/lib/capybara/spec/views/frame_child.erb +4 -3
  228. data/lib/capybara/spec/views/frame_one.erb +2 -1
  229. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  230. data/lib/capybara/spec/views/frame_two.erb +1 -1
  231. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  232. data/lib/capybara/spec/views/layout.erb +10 -0
  233. data/lib/capybara/spec/views/obscured.erb +47 -0
  234. data/lib/capybara/spec/views/offset.erb +33 -0
  235. data/lib/capybara/spec/views/path.erb +2 -2
  236. data/lib/capybara/spec/views/popup_one.erb +1 -1
  237. data/lib/capybara/spec/views/popup_two.erb +1 -1
  238. data/lib/capybara/spec/views/react.erb +45 -0
  239. data/lib/capybara/spec/views/scroll.erb +21 -0
  240. data/lib/capybara/spec/views/spatial.erb +31 -0
  241. data/lib/capybara/spec/views/tables.erb +68 -1
  242. data/lib/capybara/spec/views/with_animation.erb +81 -0
  243. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  244. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  245. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  246. data/lib/capybara/spec/views/with_hover.erb +3 -2
  247. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  248. data/lib/capybara/spec/views/with_html.erb +43 -10
  249. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  250. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  251. data/lib/capybara/spec/views/with_js.erb +30 -5
  252. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  253. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  254. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  255. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  256. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  257. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  258. data/lib/capybara/spec/views/with_windows.erb +1 -1
  259. data/lib/capybara/spec/views/within_frames.erb +1 -1
  260. data/lib/capybara/version.rb +1 -1
  261. data/lib/capybara/window.rb +31 -25
  262. data/lib/capybara.rb +120 -100
  263. data/spec/basic_node_spec.rb +59 -34
  264. data/spec/capybara_spec.rb +54 -52
  265. data/spec/css_builder_spec.rb +101 -0
  266. data/spec/css_splitter_spec.rb +38 -0
  267. data/spec/dsl_spec.rb +80 -53
  268. data/spec/filter_set_spec.rb +24 -7
  269. data/spec/fixtures/certificate.pem +25 -0
  270. data/spec/fixtures/key.pem +27 -0
  271. data/spec/fixtures/selenium_driver_rspec_failure.rb +4 -4
  272. data/spec/fixtures/selenium_driver_rspec_success.rb +4 -4
  273. data/spec/minitest_spec.rb +38 -5
  274. data/spec/minitest_spec_spec.rb +88 -62
  275. data/spec/per_session_config_spec.rb +5 -5
  276. data/spec/rack_test_spec.rb +169 -115
  277. data/spec/regexp_dissassembler_spec.rb +250 -0
  278. data/spec/result_spec.rb +86 -33
  279. data/spec/rspec/features_spec.rb +26 -23
  280. data/spec/rspec/scenarios_spec.rb +8 -4
  281. data/spec/rspec/shared_spec_matchers.rb +393 -363
  282. data/spec/rspec/views_spec.rb +4 -3
  283. data/spec/rspec_matchers_spec.rb +10 -10
  284. data/spec/rspec_spec.rb +109 -85
  285. data/spec/sauce_spec_chrome.rb +43 -0
  286. data/spec/selector_spec.rb +391 -61
  287. data/spec/selenium_spec_chrome.rb +180 -41
  288. data/spec/selenium_spec_chrome_remote.rb +101 -0
  289. data/spec/selenium_spec_edge.rb +28 -8
  290. data/spec/selenium_spec_firefox.rb +211 -0
  291. data/spec/selenium_spec_firefox_remote.rb +80 -0
  292. data/spec/selenium_spec_ie.rb +127 -11
  293. data/spec/selenium_spec_safari.rb +156 -0
  294. data/spec/server_spec.rb +177 -78
  295. data/spec/session_spec.rb +52 -16
  296. data/spec/shared_selenium_node.rb +79 -0
  297. data/spec/shared_selenium_session.rb +455 -123
  298. data/spec/spec_helper.rb +91 -2
  299. data/spec/xpath_builder_spec.rb +93 -0
  300. metadata +343 -42
  301. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  302. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  303. data/.yard/yard_extensions.rb +0 -78
  304. data/lib/capybara/rspec/compound.rb +0 -90
  305. data/lib/capybara/spec/session/assert_selector.rb +0 -149
  306. data/lib/capybara/spec/session/source_spec.rb +0 -0
  307. data/lib/capybara/spec/views/with_title.erb +0 -5
  308. data/spec/selenium_spec_marionette.rb +0 -143
@@ -5,43 +5,65 @@ require 'capybara/selector/filter'
5
5
  module Capybara
6
6
  class Selector
7
7
  class FilterSet
8
- attr_reader :descriptions
8
+ attr_reader :node_filters, :expression_filters
9
9
 
10
10
  def initialize(name, &block)
11
11
  @name = name
12
- @descriptions = []
13
- instance_eval(&block)
12
+ @node_filters = {}
13
+ @expression_filters = {}
14
+ @descriptions = Hash.new { |hsh, key| hsh[key] = [] }
15
+ instance_eval(&block) if block
14
16
  end
15
17
 
16
- def filter(name, *types_and_options, &block)
17
- 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
18
22
  end
23
+ alias_method :filter, :node_filter
19
24
 
20
- def expression_filter(name, *types_and_options, &block)
21
- 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)
22
27
  end
23
28
 
24
- def describe(&block)
25
- descriptions.push block
29
+ def describe(what = nil, &block)
30
+ case what
31
+ when nil
32
+ undeclared_descriptions.push block
33
+ when :node_filters
34
+ node_filter_descriptions.push block
35
+ when :expression_filters
36
+ expression_filter_descriptions.push block
37
+ else
38
+ raise ArgumentError, 'Unknown description type'
39
+ end
26
40
  end
27
41
 
28
- def description(**options)
42
+ def description(node_filters: true, expression_filters: true, **options)
29
43
  opts = options_with_defaults(options)
30
- @descriptions.map do |desc|
31
- desc.call(opts).to_s
32
- end.join
44
+ description = +''
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
48
+ description
33
49
  end
34
50
 
35
- def filters
36
- @filters ||= {}
51
+ def descriptions
52
+ Capybara::Helpers.warn 'DEPRECATED: FilterSet#descriptions is deprecated without replacement'
53
+ [undeclared_descriptions, node_filter_descriptions, expression_filter_descriptions].flatten
37
54
  end
38
55
 
39
- def node_filters
40
- filters.reject { |_n, f| f.nil? || f.is_a?(Filters::ExpressionFilter) }.freeze
41
- end
56
+ def import(name, filters = nil)
57
+ filter_selector = filters.nil? ? ->(*) { true } : ->(filter_name, _) { filters.include? filter_name }
42
58
 
43
- def expression_filters
44
- filters.select { |_n, f| f.nil? || f.is_a?(Filters::ExpressionFilter) }.freeze
59
+ self.class[name].tap do |f_set|
60
+ expression_filters.merge!(f_set.expression_filters.select(&filter_selector))
61
+ node_filters.merge!(f_set.node_filters.select(&filter_selector))
62
+ f_set.undeclared_descriptions.each { |desc| describe(&desc) }
63
+ f_set.expression_filter_descriptions.each { |desc| describe(:expression_filters, &desc) }
64
+ f_set.node_filter_descriptions.each { |desc| describe(:node_filters, &desc) }
65
+ end
66
+ self
45
67
  end
46
68
 
47
69
  class << self
@@ -49,6 +71,10 @@ module Capybara
49
71
  @filter_sets ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
50
72
  end
51
73
 
74
+ def [](name)
75
+ all.fetch(name.to_sym) { |set_name| raise ArgumentError, "Unknown filter set (:#{set_name})" }
76
+ end
77
+
52
78
  def add(name, &block)
53
79
  all[name.to_sym] = FilterSet.new(name.to_sym, &block)
54
80
  end
@@ -58,19 +84,38 @@ module Capybara
58
84
  end
59
85
  end
60
86
 
87
+ protected
88
+
89
+ def undeclared_descriptions
90
+ @descriptions[:undeclared]
91
+ end
92
+
93
+ def node_filter_descriptions
94
+ @descriptions[:node_filters]
95
+ end
96
+
97
+ def expression_filter_descriptions
98
+ @descriptions[:expression_filters]
99
+ end
100
+
61
101
  private
62
102
 
63
103
  def options_with_defaults(options)
64
- options = options.dup
65
- filters.each do |name, filter|
66
- options[name] = filter.default if filter.default? && !options.key?(name)
104
+ expression_filters.chain(node_filters)
105
+ .select { |_n, filter| filter.default? }
106
+ .each_with_object(options.dup) do |(name, filter), opts|
107
+ opts[name] = filter.default unless opts.key?(name)
67
108
  end
68
- options
69
109
  end
70
110
 
71
- def add_filter(name, filter_class, *types, **options, &block)
72
- types.each { |k| options[k] = true }
73
- filters[name] = filter_class.new(name, block, options)
111
+ def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
112
+ types.each { |type| options[type] = true }
113
+ if matcher && options[:default]
114
+ raise 'ArgumentError', ':default option is not supported for filters with a :matcher option'
115
+ end
116
+
117
+ filter = filter_class.new(name, matcher, block, **options)
118
+ (filter_class <= Filters::ExpressionFilter ? @expression_filters : @node_filters)[name] = filter
74
119
  end
75
120
  end
76
121
  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,10 +24,52 @@ module Capybara
23
24
  @options.key?(:skip_if) && value == @options[:skip_if]
24
25
  end
25
26
 
27
+ def format
28
+ @options[:format]
29
+ end
30
+
31
+ def matcher?
32
+ !@matcher.nil?
33
+ end
34
+
35
+ def boolean?
36
+ !!@options[:boolean]
37
+ end
38
+
39
+ def handles_option?(option_name)
40
+ if matcher?
41
+ @matcher.match? option_name
42
+ else
43
+ @name == option_name
44
+ end
45
+ end
46
+
26
47
  private
27
48
 
49
+ def apply(subject, name, value, skip_value, ctx)
50
+ return skip_value if skip?(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
57
+
58
+ if @block.arity == 2
59
+ filter_context(ctx).instance_exec(subject, value, &@block)
60
+ else
61
+ filter_context(ctx).instance_exec(subject, name, value, &@block)
62
+ end
63
+ end
64
+
65
+ def filter_context(context)
66
+ context || @block.binding.receiver
67
+ end
68
+
28
69
  def valid_value?(value)
29
- !@options.key?(:valid_values) || Array(@options[:valid_values]).include?(value)
70
+ return true unless @options.key?(:valid_values)
71
+
72
+ Array(@options[:valid_values]).any? { |valid| valid === value } # rubocop:disable Style/CaseEquality
30
73
  end
31
74
  end
32
75
  end
@@ -6,17 +6,16 @@ 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, selector)
10
+ apply(expr, name, value, expr, selector)
13
11
  end
14
12
  end
15
13
 
16
14
  class IdentityExpressionFilter < ExpressionFilter
17
- def initialize; end
15
+ def initialize(name); super(name, nil, nil); end
18
16
  def default?; false; end
19
- def apply_filter(expr, _value); expr; end
17
+ def matcher?; false; end
18
+ def apply_filter(expr, _name, _value, _ctx); expr; end
20
19
  end
21
20
  end
22
21
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selector/filters/base'
4
+
5
+ module Capybara
6
+ class Selector
7
+ module Filters
8
+ class LocatorFilter < NodeFilter
9
+ def initialize(block, **options)
10
+ super(nil, nil, block, **options)
11
+ end
12
+
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)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -6,10 +6,24 @@ 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 initialize(name, matcher, block, **options)
10
+ super
11
+ @block = if boolean?
12
+ proc do |node, value|
13
+ error_cnt = errors.size
14
+ block.call(node, value).tap do |res|
15
+ add_error("Expected #{name} #{value} but it wasn't") if !res && error_cnt == errors.size
16
+ end
17
+ end
18
+ else
19
+ block
20
+ end
21
+ end
22
+
23
+ def matches?(node, name, value, context = nil)
24
+ apply(node, name, value, true, context)
25
+ rescue Capybara::ElementNotFound
26
+ false
13
27
  end
14
28
  end
15
29
  end
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'regexp_parser'
4
+
5
+ module Capybara
6
+ class Selector
7
+ # @api private
8
+ class RegexpDisassembler
9
+ def initialize(regexp)
10
+ @regexp = regexp
11
+ end
12
+
13
+ def alternated_substrings
14
+ @alternated_substrings ||= begin
15
+ or_strings = process(alternation: true)
16
+ remove_or_covered(or_strings)
17
+ or_strings.any?(&:empty?) ? [] : or_strings
18
+ end
19
+ end
20
+
21
+ def substrings
22
+ @substrings ||= begin
23
+ strs = process(alternation: false).first
24
+ remove_and_covered(strs)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def remove_and_covered(strings)
31
+ # delete_if is documented to modify the array after every block iteration - this doesn't appear to be true
32
+ # uniq the strings to prevent identical strings from removing each other
33
+ strings.uniq!
34
+
35
+ # If we have "ab" and "abcd" required - only need to check for "abcd"
36
+ strings.delete_if do |sub_string|
37
+ strings.any? do |cover_string|
38
+ next if sub_string.equal? cover_string
39
+
40
+ cover_string.include?(sub_string)
41
+ end
42
+ end
43
+ end
44
+
45
+ def remove_or_covered(or_series)
46
+ # If we are going to match `("a" and "b") or ("ade" and "bce")` it only makes sense to match ("a" and "b")
47
+
48
+ # Ensure minimum sets of strings are being or'd
49
+ or_series.each { |strs| remove_and_covered(strs) }
50
+
51
+ # Remove any of the alternated string series that fully contain any other string series
52
+ or_series.delete_if do |and_strs|
53
+ or_series.any? do |and_strs2|
54
+ next if and_strs.equal? and_strs2
55
+
56
+ remove_and_covered(and_strs + and_strs2) == and_strs
57
+ end
58
+ end
59
+ end
60
+
61
+ def process(alternation:)
62
+ strs = extract_strings(Regexp::Parser.parse(@regexp), alternation: alternation)
63
+ strs = collapse(combine(strs).map(&:flatten))
64
+ strs.each { |str| str.map!(&:upcase) } if @regexp.casefold?
65
+ strs
66
+ end
67
+
68
+ def combine(strs)
69
+ suffixes = [[]]
70
+ strs.reverse_each do |str|
71
+ if str.is_a? Set
72
+ prefixes = str.each_with_object([]) { |s, memo| memo.concat combine(s) }
73
+
74
+ result = []
75
+ prefixes.product(suffixes) { |pair| result << pair.flatten(1) }
76
+ suffixes = result
77
+ else
78
+ suffixes.each { |arr| arr.unshift str }
79
+ end
80
+ end
81
+ suffixes
82
+ end
83
+
84
+ def collapse(strs)
85
+ strs.map do |substrings|
86
+ substrings.slice_before(&:nil?).map(&:join).reject(&:empty?).uniq
87
+ end
88
+ end
89
+
90
+ def extract_strings(expression, alternation: false)
91
+ Expression.new(expression).extract_strings(alternation)
92
+ end
93
+
94
+ # @api private
95
+ class Expression
96
+ def initialize(exp)
97
+ @exp = exp
98
+ end
99
+
100
+ def extract_strings(process_alternatives)
101
+ strings = []
102
+ each do |exp|
103
+ next if exp.ignore?
104
+
105
+ next strings.push(nil) if exp.optional? && !process_alternatives
106
+
107
+ next strings.push(exp.alternative_strings) if exp.alternation? && process_alternatives
108
+
109
+ strings.concat(exp.strings(process_alternatives))
110
+ end
111
+ strings
112
+ end
113
+
114
+ protected
115
+
116
+ def alternation?
117
+ (type == :meta) && !terminal?
118
+ end
119
+
120
+ def optional?
121
+ min_repeat.zero?
122
+ end
123
+
124
+ def terminal?
125
+ @exp.terminal?
126
+ end
127
+
128
+ def strings(process_alternatives)
129
+ if indeterminate?
130
+ [nil]
131
+ elsif terminal?
132
+ terminal_strings
133
+ elsif optional?
134
+ optional_strings
135
+ else
136
+ repeated_strings(process_alternatives)
137
+ end
138
+ end
139
+
140
+ def terminal_strings
141
+ text = case @exp.type
142
+ when :literal then @exp.text
143
+ when :escape then @exp.char
144
+ else
145
+ return [nil]
146
+ end
147
+
148
+ optional? ? options_set(text) : repeat_set(text)
149
+ end
150
+
151
+ def optional_strings
152
+ options_set(extract_strings(true))
153
+ end
154
+
155
+ def repeated_strings(process_alternatives)
156
+ repeat_set extract_strings(process_alternatives)
157
+ end
158
+
159
+ def alternative_strings
160
+ alts = alternatives.map { |sub_exp| sub_exp.extract_strings(alternation: true) }
161
+ alts.all?(&:any?) ? Set.new(alts) : nil
162
+ end
163
+
164
+ def ignore?
165
+ [Regexp::Expression::Assertion::NegativeLookahead,
166
+ Regexp::Expression::Assertion::NegativeLookbehind].any? { |klass| @exp.is_a? klass }
167
+ end
168
+
169
+ private
170
+
171
+ def indeterminate?
172
+ %i[meta set].include?(type)
173
+ end
174
+
175
+ def min_repeat
176
+ @exp.repetitions.begin
177
+ end
178
+
179
+ def max_repeat
180
+ @exp.repetitions.end
181
+ end
182
+
183
+ def fixed_repeat?
184
+ min_repeat == max_repeat
185
+ end
186
+
187
+ def type
188
+ @exp.type
189
+ end
190
+
191
+ def repeat_set(str)
192
+ strs = Array(str * min_repeat)
193
+ strs.push(nil) unless fixed_repeat?
194
+ strs
195
+ end
196
+
197
+ def options_set(strs)
198
+ strs = [Set.new([[''], Array(strs)])]
199
+ strs.push(nil) unless max_repeat == 1
200
+ strs
201
+ end
202
+
203
+ def alternatives
204
+ @exp.alternatives.map { |exp| Expression.new(exp) }
205
+ end
206
+
207
+ def each
208
+ @exp.each { |exp| yield Expression.new(exp) }
209
+ end
210
+ end
211
+ private_constant :Expression
212
+ end
213
+ end
214
+ end