capybara 2.13.0 → 2.18.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 (122) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +218 -18
  3. data/README.md +54 -23
  4. data/lib/capybara/config.rb +132 -0
  5. data/lib/capybara/cucumber.rb +1 -0
  6. data/lib/capybara/driver/base.rb +14 -0
  7. data/lib/capybara/dsl.rb +1 -3
  8. data/lib/capybara/helpers.rb +3 -3
  9. data/lib/capybara/minitest/spec.rb +14 -37
  10. data/lib/capybara/minitest.rb +95 -114
  11. data/lib/capybara/node/actions.rb +10 -10
  12. data/lib/capybara/node/base.rb +7 -2
  13. data/lib/capybara/node/element.rb +9 -3
  14. data/lib/capybara/node/finders.rb +92 -18
  15. data/lib/capybara/node/matchers.rb +21 -9
  16. data/lib/capybara/node/simple.rb +5 -0
  17. data/lib/capybara/queries/ancestor_query.rb +25 -0
  18. data/lib/capybara/queries/base_query.rb +12 -3
  19. data/lib/capybara/queries/current_path_query.rb +13 -9
  20. data/lib/capybara/queries/selector_query.rb +62 -23
  21. data/lib/capybara/queries/sibling_query.rb +25 -0
  22. data/lib/capybara/queries/text_query.rb +10 -5
  23. data/lib/capybara/queries/title_query.rb +1 -0
  24. data/lib/capybara/rack_test/browser.rb +13 -5
  25. data/lib/capybara/rack_test/driver.rb +6 -1
  26. data/lib/capybara/rack_test/form.rb +4 -3
  27. data/lib/capybara/rack_test/node.rb +1 -1
  28. data/lib/capybara/rspec/compound.rb +95 -0
  29. data/lib/capybara/rspec/matcher_proxies.rb +45 -0
  30. data/lib/capybara/rspec/matchers.rb +108 -7
  31. data/lib/capybara/rspec.rb +3 -1
  32. data/lib/capybara/selector/filter.rb +13 -41
  33. data/lib/capybara/selector/filter_set.rb +30 -4
  34. data/lib/capybara/selector/filters/base.rb +33 -0
  35. data/lib/capybara/selector/filters/expression_filter.rb +40 -0
  36. data/lib/capybara/selector/filters/node_filter.rb +27 -0
  37. data/lib/capybara/selector/selector.rb +36 -15
  38. data/lib/capybara/selector.rb +63 -42
  39. data/lib/capybara/selenium/driver.rb +177 -33
  40. data/lib/capybara/selenium/node.rb +106 -55
  41. data/lib/capybara/server.rb +6 -5
  42. data/lib/capybara/session/config.rb +114 -0
  43. data/lib/capybara/session/matchers.rb +15 -4
  44. data/lib/capybara/session.rb +178 -65
  45. data/lib/capybara/spec/fixtures/no_extension +1 -0
  46. data/lib/capybara/spec/public/test.js +18 -3
  47. data/lib/capybara/spec/session/accept_alert_spec.rb +9 -1
  48. data/lib/capybara/spec/session/accept_prompt_spec.rb +29 -1
  49. data/lib/capybara/spec/session/all_spec.rb +13 -1
  50. data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
  51. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +24 -8
  52. data/lib/capybara/spec/session/assert_selector.rb +1 -1
  53. data/lib/capybara/spec/session/assert_text.rb +8 -0
  54. data/lib/capybara/spec/session/assert_title.rb +22 -9
  55. data/lib/capybara/spec/session/attach_file_spec.rb +8 -1
  56. data/lib/capybara/spec/session/check_spec.rb +4 -4
  57. data/lib/capybara/spec/session/choose_spec.rb +2 -2
  58. data/lib/capybara/spec/session/click_button_spec.rb +1 -1
  59. data/lib/capybara/spec/session/click_link_or_button_spec.rb +3 -3
  60. data/lib/capybara/spec/session/click_link_spec.rb +1 -1
  61. data/lib/capybara/spec/session/current_url_spec.rb +3 -3
  62. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  63. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -1
  64. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
  65. data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -1
  66. data/lib/capybara/spec/session/fill_in_spec.rb +8 -2
  67. data/lib/capybara/spec/session/find_field_spec.rb +1 -0
  68. data/lib/capybara/spec/session/find_spec.rb +8 -6
  69. data/lib/capybara/spec/session/first_spec.rb +10 -5
  70. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  71. data/lib/capybara/spec/session/has_css_spec.rb +11 -0
  72. data/lib/capybara/spec/session/has_current_path_spec.rb +52 -7
  73. data/lib/capybara/spec/session/has_link_spec.rb +4 -4
  74. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  75. data/lib/capybara/spec/session/has_select_spec.rb +64 -6
  76. data/lib/capybara/spec/session/has_selector_spec.rb +1 -3
  77. data/lib/capybara/spec/session/has_text_spec.rb +5 -3
  78. data/lib/capybara/spec/session/has_title_spec.rb +4 -2
  79. data/lib/capybara/spec/session/has_xpath_spec.rb +5 -3
  80. data/lib/capybara/spec/session/node_spec.rb +50 -26
  81. data/lib/capybara/spec/session/refresh_spec.rb +28 -0
  82. data/lib/capybara/spec/session/reset_session_spec.rb +3 -3
  83. data/lib/capybara/spec/session/select_spec.rb +3 -2
  84. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  85. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  86. data/lib/capybara/spec/session/unselect_spec.rb +2 -2
  87. data/lib/capybara/spec/session/visit_spec.rb +56 -1
  88. data/lib/capybara/spec/session/window/become_closed_spec.rb +11 -11
  89. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +11 -9
  90. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -4
  91. data/lib/capybara/spec/session/window/within_window_spec.rb +27 -2
  92. data/lib/capybara/spec/spec_helper.rb +28 -4
  93. data/lib/capybara/spec/test_app.rb +3 -1
  94. data/lib/capybara/spec/views/form.erb +27 -1
  95. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  96. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  97. data/lib/capybara/spec/views/with_hover.erb +5 -0
  98. data/lib/capybara/spec/views/with_html.erb +33 -2
  99. data/lib/capybara/spec/views/with_js.erb +12 -0
  100. data/lib/capybara/spec/views/with_windows.erb +4 -0
  101. data/lib/capybara/version.rb +1 -1
  102. data/lib/capybara/window.rb +1 -1
  103. data/lib/capybara.rb +102 -124
  104. data/spec/capybara_spec.rb +43 -21
  105. data/spec/dsl_spec.rb +1 -0
  106. data/spec/filter_set_spec.rb +28 -0
  107. data/spec/minitest_spec.rb +9 -1
  108. data/spec/minitest_spec_spec.rb +19 -5
  109. data/spec/per_session_config_spec.rb +67 -0
  110. data/spec/result_spec.rb +20 -0
  111. data/spec/rspec/shared_spec_matchers.rb +148 -44
  112. data/spec/rspec/views_spec.rb +4 -0
  113. data/spec/rspec_matchers_spec.rb +46 -0
  114. data/spec/rspec_spec.rb +77 -0
  115. data/spec/selector_spec.rb +2 -1
  116. data/spec/selenium_spec_chrome.rb +25 -17
  117. data/spec/selenium_spec_firefox.rb +2 -1
  118. data/spec/selenium_spec_marionette.rb +18 -5
  119. data/spec/session_spec.rb +44 -0
  120. data/spec/shared_selenium_session.rb +72 -8
  121. data/spec/spec_helper.rb +4 -0
  122. metadata +55 -8
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'rack/test'
3
3
  require 'rack/utils'
4
- require 'mime/types'
4
+ require 'mini_mime'
5
5
  require 'nokogiri'
6
6
  require 'cgi'
7
7
 
@@ -15,6 +15,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
15
15
 
16
16
  def initialize(app, options={})
17
17
  raise ArgumentError, "rack-test requires a rack application, but none was given" unless app
18
+ @session = nil
18
19
  @app = app
19
20
  @options = DEFAULT_OPTIONS.merge(options)
20
21
  end
@@ -43,6 +44,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
43
44
  browser.visit(path, attributes)
44
45
  end
45
46
 
47
+ def refresh
48
+ browser.refresh
49
+ end
50
+
46
51
  def submit(method, path, attributes)
47
52
  browser.submit(method, path, attributes)
48
53
  end
@@ -13,6 +13,8 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
13
13
  def original_filename; ""; end
14
14
  def content_type; "application/octet-stream"; end
15
15
  def path; @empty_file.path; end
16
+ def size; 0; end
17
+ def read; ""; end
16
18
  end
17
19
 
18
20
  def params(button)
@@ -42,9 +44,8 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
42
44
  if (value = field['value']).to_s.empty?
43
45
  NilUploadedFile.new
44
46
  else
45
- types = MIME::Types.type_for(value)
46
- content_type = types.sort_by.with_index { |type, idx| [type.obsolete? ? 1 : 0, idx] }.first.to_s
47
- Rack::Test::UploadedFile.new(value, content_type)
47
+ mime_info = MiniMime.lookup_by_filename(value)
48
+ Rack::Test::UploadedFile.new(value, (mime_info && mime_info.content_type).to_s)
48
49
  end
49
50
  merge_param!(params, field['name'].to_s, file)
50
51
  else
@@ -141,7 +141,7 @@ private
141
141
 
142
142
  # a reference to the select node if this is an option node
143
143
  def select_node
144
- find_xpath('./ancestor::select').first
144
+ find_xpath('./ancestor::select[1]').first
145
145
  end
146
146
 
147
147
  def type
@@ -0,0 +1,95 @@
1
+ module Capybara
2
+ module RSpecMatchers
3
+ module Compound
4
+ include ::RSpec::Matchers::Composable
5
+
6
+ def and(matcher)
7
+ Capybara::RSpecMatchers::Compound::And.new(self,matcher)
8
+ end
9
+
10
+ def and_then(matcher)
11
+ ::RSpec::Matchers::BuiltIn::Compound::And.new(self, matcher)
12
+ end
13
+
14
+ def or(matcher)
15
+ Capybara::RSpecMatchers::Compound::Or.new(self, matcher)
16
+ end
17
+
18
+
19
+ class CapybaraEvaluator
20
+ def initialize(actual, matcher_1, matcher_2)
21
+ @actual = actual
22
+ @matcher_1 = matcher_1
23
+ @matcher_2 = matcher_2
24
+ @match_results = Hash.new { |h, matcher| h[matcher] = matcher.matches?(@actual) }
25
+ end
26
+
27
+ def matcher_matches?(matcher)
28
+ @match_results[matcher]
29
+ end
30
+
31
+ def reset
32
+ @match_results.clear
33
+ end
34
+ end
35
+
36
+ class And < ::RSpec::Matchers::BuiltIn::Compound::And
37
+
38
+ private
39
+
40
+ def match(_expected, actual)
41
+ @evaluator = CapybaraEvaluator.new(actual, matcher_1, matcher_2)
42
+ syncer = sync_element(actual)
43
+ begin
44
+ syncer.synchronize do
45
+ @evaluator.reset
46
+ raise ::Capybara::ElementNotFound unless [matcher_1_matches?, matcher_2_matches?].all?
47
+ true
48
+ end
49
+ rescue
50
+ false
51
+ end
52
+ end
53
+
54
+ def sync_element(el)
55
+ if el.respond_to? :synchronize
56
+ el
57
+ elsif el.respond_to? :current_scope
58
+ el.current_scope
59
+ else
60
+ Capybara.string(el)
61
+ end
62
+ end
63
+ end
64
+
65
+ class Or < ::RSpec::Matchers::BuiltIn::Compound::Or
66
+
67
+ private
68
+
69
+ def match(_expected, actual)
70
+ @evaluator = CapybaraEvaluator.new(actual, matcher_1, matcher_2)
71
+ syncer = sync_element(actual)
72
+ begin
73
+ syncer.synchronize do
74
+ @evaluator.reset
75
+ raise ::Capybara::ElementNotFound unless [matcher_1_matches?, matcher_2_matches?].any?
76
+ true
77
+ end
78
+ rescue
79
+ false
80
+ end
81
+ end
82
+
83
+ def sync_element(el)
84
+ if el.respond_to? :synchronize
85
+ el
86
+ elsif el.respond_to? :current_scope
87
+ el.current_scope
88
+ else
89
+ Capybara.string(el)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ module Capybara
3
+ module RSpecMatcherProxies
4
+ def all(*args)
5
+ if defined?(::RSpec::Matchers::BuiltIn::All) && args.first.respond_to?(:matches?)
6
+ ::RSpec::Matchers::BuiltIn::All.new(*args)
7
+ else
8
+ find_all(*args)
9
+ end
10
+ end
11
+
12
+ def within(*args)
13
+ if block_given?
14
+ within_element(*args, &Proc.new)
15
+ else
16
+ be_within(*args)
17
+ end
18
+ end
19
+ end
20
+
21
+ module DSL
22
+ class <<self
23
+ remove_method :included
24
+
25
+ def included(base)
26
+ warn "including Capybara::DSL in the global scope is not recommended!" if base == Object
27
+
28
+ if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers)
29
+ base.send(:include, ::Capybara::RSpecMatcherProxies)
30
+ end
31
+
32
+ super
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ if defined?(::RSpec::Matchers)
39
+ module ::RSpec::Matchers
40
+ def self.included(base)
41
+ base.send(:include, ::Capybara::RSpecMatcherProxies) if base.include?(::Capybara::DSL)
42
+ super
43
+ end
44
+ end
45
+ end
@@ -2,12 +2,15 @@
2
2
  module Capybara
3
3
  module RSpecMatchers
4
4
  class Matcher
5
- include ::RSpec::Matchers::Composable if defined?(::RSpec::Expectations::Version) && (Gem::Version.new(RSpec::Expectations::Version::STRING) >= Gem::Version.new('3.0'))
5
+ if defined?(::RSpec::Expectations::Version) && (Gem::Version.new(RSpec::Expectations::Version::STRING) >= Gem::Version.new('3.0'))
6
+ require 'capybara/rspec/compound'
7
+ include ::Capybara::RSpecMatchers::Compound
8
+ end
6
9
 
7
10
  attr_reader :failure_message, :failure_message_when_negated
8
11
 
9
12
  def wrap(actual)
10
- if actual.respond_to?("has_selector?")
13
+ @context_el = if actual.respond_to?("has_selector?")
11
14
  actual
12
15
  else
13
16
  Capybara.string(actual.to_s)
@@ -33,10 +36,29 @@ module Capybara
33
36
  @failure_message_when_negated = e.message
34
37
  return false
35
38
  end
39
+
40
+ def session_query_args
41
+ if @args.last.is_a? Hash
42
+ @args.last[:session_options] = session_options
43
+ else
44
+ @args.push(session_options: session_options)
45
+ end
46
+ @args
47
+ end
48
+
49
+ def session_options
50
+ @context_el ||= nil
51
+ if @context_el.respond_to? :session_options
52
+ @context_el.session_options
53
+ elsif @context_el.respond_to? :current_scope
54
+ @context_el.current_scope.session_options
55
+ else
56
+ Capybara.session_options
57
+ end
58
+ end
36
59
  end
37
60
 
38
61
  class HaveSelector < Matcher
39
-
40
62
  def initialize(*args, &filter_block)
41
63
  @args = args
42
64
  @filter_block = filter_block
@@ -55,7 +77,45 @@ module Capybara
55
77
  end
56
78
 
57
79
  def query
58
- @query ||= Capybara::Queries::SelectorQuery.new(*@args, &@filter_block)
80
+ @query ||= Capybara::Queries::SelectorQuery.new(*session_query_args, &@filter_block)
81
+ end
82
+ end
83
+
84
+ class HaveAllSelectors < Matcher
85
+ def initialize(*args, &filter_block)
86
+ @args = args
87
+ @filter_block = filter_block
88
+ end
89
+
90
+ def matches?(actual)
91
+ wrap_matches?(actual){ |el| el.assert_all_of_selectors(*@args, &@filter_block) }
92
+ end
93
+
94
+ def does_not_match?(actual)
95
+ raise ArgumentError, "The have_all_selectors matcher does not support use with not_to/should_not"
96
+ end
97
+
98
+ def description
99
+ "have all selectors"
100
+ end
101
+ end
102
+
103
+ class HaveNoSelectors < Matcher
104
+ def initialize(*args, &filter_block)
105
+ @args = args
106
+ @filter_block = filter_block
107
+ end
108
+
109
+ def matches?(actual)
110
+ wrap_matches?(actual){ |el| el.assert_none_of_selectors(*@args, &@filter_block) }
111
+ end
112
+
113
+ def does_not_match?(actual)
114
+ raise ArgumentError, "The have_none_of_selectors matcher does not support use with not_to/should_not"
115
+ end
116
+
117
+ def description
118
+ "have no selectors"
59
119
  end
60
120
  end
61
121
 
@@ -73,7 +133,7 @@ module Capybara
73
133
  end
74
134
 
75
135
  def query
76
- @query ||= Capybara::Queries::MatchQuery.new(*@args)
136
+ @query ||= Capybara::Queries::MatchQuery.new(*session_query_args, &@filter_block)
77
137
  end
78
138
  end
79
139
 
@@ -155,11 +215,12 @@ module Capybara
155
215
 
156
216
  class BecomeClosed
157
217
  def initialize(options)
158
- @wait_time = Capybara::Queries::BaseQuery.wait(options)
218
+ @options = options
159
219
  end
160
220
 
161
221
  def matches?(window)
162
222
  @window = window
223
+ @wait_time = Capybara::Queries::BaseQuery.wait(@options, window.session.config.default_max_wait_time)
163
224
  start_time = Capybara::Helpers.monotonic_time
164
225
  while window.exists?
165
226
  return false if (Capybara::Helpers.monotonic_time - start_time) > @wait_time
@@ -181,10 +242,26 @@ module Capybara
181
242
  alias_method :failure_message_for_should_not, :failure_message_when_negated
182
243
  end
183
244
 
245
+ # RSpec matcher for whether the element(s) matching a given selector exist
246
+ # See {Capybara::Node::Matcher#assert_selector}
184
247
  def have_selector(*args, &optional_filter_block)
185
248
  HaveSelector.new(*args, &optional_filter_block)
186
249
  end
187
250
 
251
+ # RSpec matcher for whether the element(s) matching a group of selectors exist
252
+ # See {Capybara::Node::Matcher#assert_all_of_selectors}
253
+ def have_all_of_selectors(*args, &optional_filter_block)
254
+ HaveAllSelectors.new(*args, &optional_filter_block)
255
+ end
256
+
257
+ # RSpec matcher for whether no element(s) matching a group of selectors exist
258
+ # See {Capybara::Node::Matcher#assert_none_of_selectors}
259
+ def have_none_of_selectors(*args, &optional_filter_block)
260
+ HaveNoSelectors.new(*args, &optional_filter_block)
261
+ end
262
+
263
+ # RSpec matcher for whether the current element matches a given selector
264
+ # See {Capybara::Node::Matchers#assert_matches_selector}
188
265
  def match_selector(*args, &optional_filter_block)
189
266
  MatchSelector.new(*args, &optional_filter_block)
190
267
  end
@@ -193,22 +270,30 @@ module Capybara
193
270
  ::RSpec::Matchers.define_negated_matcher :not_match_selector, :match_selector if defined?(::RSpec::Expectations::Version) && (Gem::Version.new(RSpec::Expectations::Version::STRING) >= Gem::Version.new('3.1'))
194
271
 
195
272
 
273
+ # RSpec matcher for whether elements(s) matching a given xpath selector exist
274
+ # See {Capybara::Node::Matchers#has_xpath?}
196
275
  def have_xpath(xpath, options={}, &optional_filter_block)
197
276
  HaveSelector.new(:xpath, xpath, options, &optional_filter_block)
198
277
  end
199
278
 
279
+ # RSpec matcher for whether the current element matches a given xpath selector
200
280
  def match_xpath(xpath, options={}, &optional_filter_block)
201
281
  MatchSelector.new(:xpath, xpath, options, &optional_filter_block)
202
282
  end
203
283
 
284
+ # RSpec matcher for whether elements(s) matching a given css selector exist
285
+ # See {Capybara::Node::Matchers#has_css?}
204
286
  def have_css(css, options={}, &optional_filter_block)
205
287
  HaveSelector.new(:css, css, options, &optional_filter_block)
206
288
  end
207
289
 
290
+ # RSpec matcher for whether the current element matches a given css selector
208
291
  def match_css(css, options={}, &optional_filter_block)
209
292
  MatchSelector.new(:css, css, options, &optional_filter_block)
210
293
  end
211
294
 
295
+ # RSpec matcher for text on the page
296
+ # See {Capybara::SessionMatchers#assert_text}
212
297
  def have_text(*args)
213
298
  HaveText.new(*args)
214
299
  end
@@ -218,40 +303,56 @@ module Capybara
218
303
  HaveTitle.new(title, options)
219
304
  end
220
305
 
306
+ # RSpec matcher for the current path
307
+ # See {Capybara::SessionMatchers#assert_current_path}
221
308
  def have_current_path(path, options = {})
222
309
  HaveCurrentPath.new(path, options)
223
310
  end
224
311
 
312
+ # RSpec matcher for links
313
+ # See {Capybara::Node::Matchers#has_link?}
225
314
  def have_link(locator=nil, options={}, &optional_filter_block)
226
315
  locator, options = nil, locator if locator.is_a? Hash
227
316
  HaveSelector.new(:link, locator, options, &optional_filter_block)
228
317
  end
229
318
 
319
+ # RSpec matcher for buttons
320
+ # See {Capybara::Node::Matchers#has_button?}
230
321
  def have_button(locator=nil, options={}, &optional_filter_block)
231
322
  locator, options = nil, locator if locator.is_a? Hash
232
323
  HaveSelector.new(:button, locator, options, &optional_filter_block)
233
324
  end
234
325
 
326
+ # RSpec matcher for links
327
+ # See {Capybara::Node::Matchers#has_field?}
235
328
  def have_field(locator=nil, options={}, &optional_filter_block)
236
329
  locator, options = nil, locator if locator.is_a? Hash
237
330
  HaveSelector.new(:field, locator, options, &optional_filter_block)
238
331
  end
239
332
 
333
+ # RSpec matcher for checked fields
334
+ # See {Capybara::Node::Matchers#has_checked_field?}
240
335
  def have_checked_field(locator=nil, options={}, &optional_filter_block)
241
336
  locator, options = nil, locator if locator.is_a? Hash
242
337
  HaveSelector.new(:field, locator, options.merge(checked: true), &optional_filter_block)
243
338
  end
244
339
 
340
+ # RSpec matcher for unchecked fields
341
+ # See {Capybara::Node::Matchers#has_unchecked_field?}
245
342
  def have_unchecked_field(locator=nil, options={}, &optional_filter_block)
246
343
  locator, options = nil, locator if locator.is_a? Hash
247
344
  HaveSelector.new(:field, locator, options.merge(unchecked: true), &optional_filter_block)
248
345
  end
249
346
 
347
+ # RSpec matcher for select elements
348
+ # See {Capybara::Node::Matchers#has_select?}
250
349
  def have_select(locator=nil, options={}, &optional_filter_block)
251
350
  locator, options = nil, locator if locator.is_a? Hash
252
351
  HaveSelector.new(:select, locator, options, &optional_filter_block)
253
352
  end
254
353
 
354
+ # RSpec matcher for table elements
355
+ # See {Capybara::Node::Matchers#has_table?}
255
356
  def have_table(locator=nil, options={}, &optional_filter_block)
256
357
  locator, options = nil, locator if locator.is_a? Hash
257
358
  HaveSelector.new(:table, locator, options, &optional_filter_block)
@@ -267,4 +368,4 @@ module Capybara
267
368
  BecomeClosed.new(options)
268
369
  end
269
370
  end
270
- end
371
+ end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require 'capybara/dsl'
3
2
  require 'rspec/core'
3
+ require 'capybara/dsl'
4
4
  require 'capybara/rspec/matchers'
5
5
  require 'capybara/rspec/features'
6
+ require 'capybara/rspec/matcher_proxies'
6
7
 
7
8
  RSpec.configure do |config|
8
9
  config.include Capybara::DSL, :type => :feature
@@ -22,6 +23,7 @@ RSpec.configure do |config|
22
23
  Capybara.use_default_driver
23
24
  end
24
25
  end
26
+
25
27
  config.before do
26
28
  if self.class.include?(Capybara::DSL)
27
29
  example = fetch_current_example.call(self)
@@ -1,47 +1,19 @@
1
1
  # frozen_string_literal: true
2
+ require 'capybara/selector/filters/node_filter'
3
+ require 'capybara/selector/filters/expression_filter'
4
+
2
5
  module Capybara
3
6
  class Selector
4
- class Filter
5
- def initialize(name, block, options={})
6
- @name = name
7
- @block = block
8
- @options = options
9
- @options[:valid_values] = [true,false] if options[:boolean]
10
- end
11
-
12
- def default?
13
- @options.has_key?(:default)
14
- end
15
-
16
- def default
17
- @options[:default]
18
- end
19
-
20
- def matches?(node, value)
21
- return true if skip?(value)
22
-
23
- if !valid_value?(value)
24
- msg = "Invalid value #{value.inspect} passed to filter #{@name} - "
25
- if default?
26
- warn msg + "defaulting to #{default}"
27
- value = default
28
- else
29
- warn msg + "skipping"
30
- return true
31
- end
32
- end
33
-
34
- @block.call(node, value)
35
- end
36
-
37
- def skip?(value)
38
- @options.has_key?(:skip_if) && value == @options[:skip_if]
39
- end
40
-
41
- private
42
-
43
- def valid_value?(value)
44
- !@options.has_key?(:valid_values) || Array(@options[:valid_values]).include?(value)
7
+ def self.const_missing(const_name)
8
+ case const_name
9
+ when :Filter
10
+ warn "DEPRECATED: Capybara::Selector::Filter is deprecated, please use Capybara::Selector::Filters::NodeFilter instead"
11
+ Filters::NodeFilter
12
+ when :ExpressionFilter
13
+ warn "DEPRECATED: Capybara::Selector::ExpressionFilter is deprecated, please use Capybara::Selector::Filters::ExpressionFilter instead"
14
+ Filters::ExpressionFilter
15
+ else
16
+ super
45
17
  end
46
18
  end
47
19
  end
@@ -13,9 +13,11 @@ module Capybara
13
13
  end
14
14
 
15
15
  def filter(name, *types_and_options, &block)
16
- options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}
17
- types_and_options.each { |k| options[k] = true}
18
- filters[name] = Filter.new(name, block, options)
16
+ add_filter(name, Filters::NodeFilter, *types_and_options, &block)
17
+ end
18
+
19
+ def expression_filter(name, *types_and_options, &block)
20
+ add_filter(name, Filters::ExpressionFilter, *types_and_options, &block)
19
21
  end
20
22
 
21
23
  def describe(&block)
@@ -23,14 +25,30 @@ module Capybara
23
25
  end
24
26
 
25
27
  def description(options={})
26
- @descriptions.map {|desc| desc.call(options).to_s }.join
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)
31
+ end
32
+
33
+ @descriptions.map do |desc|
34
+ desc.call(options_with_defaults).to_s
35
+ end.join
27
36
  end
28
37
 
29
38
  def filters
30
39
  @filters ||= {}
31
40
  end
32
41
 
42
+ def node_filters
43
+ filters.reject { |_n, f| f.nil? || f.is_a?(Filters::ExpressionFilter) }.freeze
44
+ end
45
+
46
+ def expression_filters
47
+ filters.select { |_n, f| f.nil? || f.is_a?(Filters::ExpressionFilter) }.freeze
48
+ end
49
+
33
50
  class << self
51
+
34
52
  def all
35
53
  @filter_sets ||= {}
36
54
  end
@@ -43,6 +61,14 @@ module Capybara
43
61
  all.delete(name.to_sym)
44
62
  end
45
63
  end
64
+
65
+ private
66
+
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)
71
+ end
46
72
  end
47
73
  end
48
74
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ module Capybara
3
+ class Selector
4
+ module Filters
5
+ class Base
6
+ def initialize(name, block, options={})
7
+ @name = name
8
+ @block = block
9
+ @options = options
10
+ @options[:valid_values] = [true,false] if options[:boolean]
11
+ end
12
+
13
+ def default?
14
+ @options.has_key?(:default)
15
+ end
16
+
17
+ def default
18
+ @options[:default]
19
+ end
20
+
21
+ def skip?(value)
22
+ @options.has_key?(:skip_if) && value == @options[:skip_if]
23
+ end
24
+
25
+ private
26
+
27
+ def valid_value?(value)
28
+ !@options.has_key?(:valid_values) || Array(@options[:valid_values]).include?(value)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require 'capybara/selector/filters/base'
3
+
4
+ module Capybara
5
+ class Selector
6
+ module Filters
7
+ 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)
23
+ end
24
+ end
25
+
26
+ 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
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ require 'capybara/selector/filters/base'
3
+
4
+ module Capybara
5
+ class Selector
6
+ module Filters
7
+ 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
19
+ end
20
+ end
21
+
22
+ @block.call(node, value)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end