capybara 2.13.0 → 2.18.0

Sign up to get free protection for your applications and to get access to all the features.
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