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