capybara 3.29.0 → 3.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +19 -1
  3. data/README.md +1 -1
  4. data/lib/capybara/config.rb +7 -3
  5. data/lib/capybara/helpers.rb +3 -1
  6. data/lib/capybara/node/actions.rb +23 -19
  7. data/lib/capybara/node/document.rb +2 -2
  8. data/lib/capybara/node/document_matchers.rb +3 -3
  9. data/lib/capybara/node/element.rb +10 -8
  10. data/lib/capybara/node/finders.rb +12 -10
  11. data/lib/capybara/node/matchers.rb +39 -33
  12. data/lib/capybara/node/simple.rb +3 -1
  13. data/lib/capybara/queries/ancestor_query.rb +1 -1
  14. data/lib/capybara/queries/selector_query.rb +17 -4
  15. data/lib/capybara/queries/sibling_query.rb +1 -1
  16. data/lib/capybara/rack_test/browser.rb +4 -1
  17. data/lib/capybara/rack_test/driver.rb +1 -1
  18. data/lib/capybara/rack_test/form.rb +1 -1
  19. data/lib/capybara/selector.rb +22 -16
  20. data/lib/capybara/selector/css.rb +1 -1
  21. data/lib/capybara/selector/definition.rb +2 -2
  22. data/lib/capybara/selector/definition/button.rb +7 -2
  23. data/lib/capybara/selector/definition/checkbox.rb +2 -2
  24. data/lib/capybara/selector/definition/css.rb +3 -1
  25. data/lib/capybara/selector/definition/datalist_input.rb +1 -1
  26. data/lib/capybara/selector/definition/datalist_option.rb +1 -1
  27. data/lib/capybara/selector/definition/element.rb +1 -1
  28. data/lib/capybara/selector/definition/field.rb +1 -1
  29. data/lib/capybara/selector/definition/file_field.rb +1 -1
  30. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  31. data/lib/capybara/selector/definition/label.rb +3 -1
  32. data/lib/capybara/selector/definition/radio_button.rb +2 -2
  33. data/lib/capybara/selector/definition/select.rb +1 -1
  34. data/lib/capybara/selector/definition/table.rb +5 -2
  35. data/lib/capybara/selector/filter_set.rb +11 -9
  36. data/lib/capybara/selector/filters/base.rb +6 -1
  37. data/lib/capybara/selector/filters/locator_filter.rb +1 -1
  38. data/lib/capybara/selector/selector.rb +4 -2
  39. data/lib/capybara/selenium/driver.rb +19 -11
  40. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
  41. data/lib/capybara/selenium/extensions/html5_drag.rb +6 -5
  42. data/lib/capybara/selenium/node.rb +6 -4
  43. data/lib/capybara/selenium/nodes/chrome_node.rb +7 -3
  44. data/lib/capybara/selenium/nodes/edge_node.rb +3 -1
  45. data/lib/capybara/selenium/nodes/firefox_node.rb +1 -1
  46. data/lib/capybara/server.rb +15 -3
  47. data/lib/capybara/server/checker.rb +1 -1
  48. data/lib/capybara/server/middleware.rb +20 -10
  49. data/lib/capybara/session.rb +10 -8
  50. data/lib/capybara/session/config.rb +6 -2
  51. data/lib/capybara/session/matchers.rb +6 -6
  52. data/lib/capybara/spec/public/test.js +11 -0
  53. data/lib/capybara/spec/session/all_spec.rb +15 -0
  54. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  55. data/lib/capybara/spec/session/assert_text_spec.rb +4 -0
  56. data/lib/capybara/spec/session/click_button_spec.rb +5 -0
  57. data/lib/capybara/spec/session/find_spec.rb +20 -0
  58. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  59. data/lib/capybara/spec/session/has_text_spec.rb +31 -0
  60. data/lib/capybara/spec/session/node_spec.rb +15 -0
  61. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  62. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  63. data/lib/capybara/spec/session/selectors_spec.rb +15 -2
  64. data/lib/capybara/spec/views/form.erb +5 -0
  65. data/lib/capybara/version.rb +1 -1
  66. data/spec/dsl_spec.rb +2 -2
  67. data/spec/minitest_spec_spec.rb +46 -46
  68. data/spec/regexp_dissassembler_spec.rb +45 -37
  69. data/spec/result_spec.rb +3 -3
  70. data/spec/rspec/features_spec.rb +1 -0
  71. data/spec/rspec/shared_spec_matchers.rb +3 -3
  72. data/spec/rspec_spec.rb +4 -4
  73. data/spec/selenium_spec_chrome.rb +3 -3
  74. data/spec/selenium_spec_firefox.rb +7 -2
  75. data/spec/server_spec.rb +42 -0
  76. data/spec/session_spec.rb +1 -1
  77. data/spec/shared_selenium_node.rb +3 -3
  78. data/spec/shared_selenium_session.rb +8 -7
  79. metadata +3 -3
@@ -79,14 +79,18 @@ module Capybara
79
79
 
80
80
  remove_method :app_host=
81
81
  def app_host=(url)
82
- raise ArgumentError, "Capybara.app_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}." unless url.nil? || url.match?(URI::DEFAULT_PARSER.make_regexp)
82
+ unless url.nil? || url.match?(URI::DEFAULT_PARSER.make_regexp)
83
+ raise ArgumentError, "Capybara.app_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}."
84
+ end
83
85
 
84
86
  @app_host = url
85
87
  end
86
88
 
87
89
  remove_method :default_host=
88
90
  def default_host=(url)
89
- raise ArgumentError, "Capybara.default_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}." unless url.nil? || url.match?(URI::DEFAULT_PARSER.make_regexp)
91
+ unless url.nil? || url.match?(URI::DEFAULT_PARSER.make_regexp)
92
+ raise ArgumentError, "Capybara.default_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}."
93
+ end
90
94
 
91
95
  @default_host = url
92
96
  end
@@ -20,7 +20,7 @@ module Capybara
20
20
  # @return [true]
21
21
  #
22
22
  def assert_current_path(path, **options)
23
- _verify_current_path(path, options) do |query|
23
+ _verify_current_path(path, **options) do |query|
24
24
  raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self)
25
25
  end
26
26
  end
@@ -36,7 +36,7 @@ module Capybara
36
36
  # @return [true]
37
37
  #
38
38
  def assert_no_current_path(path, **options)
39
- _verify_current_path(path, options) do |query|
39
+ _verify_current_path(path, **options) do |query|
40
40
  raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self)
41
41
  end
42
42
  end
@@ -51,7 +51,7 @@ module Capybara
51
51
  # @return [Boolean]
52
52
  #
53
53
  def has_current_path?(path, **options)
54
- make_predicate(options) { assert_current_path(path, options) }
54
+ make_predicate(options) { assert_current_path(path, **options) }
55
55
  end
56
56
 
57
57
  ##
@@ -64,13 +64,13 @@ module Capybara
64
64
  # @return [Boolean]
65
65
  #
66
66
  def has_no_current_path?(path, **options)
67
- make_predicate(options) { assert_no_current_path(path, options) }
67
+ make_predicate(options) { assert_no_current_path(path, **options) }
68
68
  end
69
69
 
70
70
  private
71
71
 
72
- def _verify_current_path(path, options)
73
- query = Capybara::Queries::CurrentPathQuery.new(path, options)
72
+ def _verify_current_path(path, **options)
73
+ query = Capybara::Queries::CurrentPathQuery.new(path, **options)
74
74
  document.synchronize(query.wait) do
75
75
  yield(query)
76
76
  end
@@ -12,10 +12,21 @@ $(function() {
12
12
  $('#drag_html5, #drag_html5_scroll').on('dragstart', function(ev){
13
13
  ev.originalEvent.dataTransfer.setData("text", ev.target.id);
14
14
  });
15
+ $('#drag_html5, #drag_html5_scroll').on('dragend', function(ev){
16
+ $(this).after('<div class="log">DragEnd with client position: ' + ev.clientX + ',' + ev.clientY)
17
+ });
15
18
  $('#drop_html5, #drop_html5_scroll').on('dragover', function(ev){
16
19
  $(this).after('<div class="log">DragOver with client position: ' + ev.clientX + ',' + ev.clientY)
17
20
  if ($(this).hasClass('drop')) { ev.preventDefault(); }
18
21
  });
22
+ $('#drop_html5, #drop_html5_scroll').on('dragleave', function(ev){
23
+ $(this).after('<div class="log">DragLeave with client position: ' + ev.clientX + ',' + ev.clientY)
24
+ if ($(this).hasClass('drop')) { ev.preventDefault(); }
25
+ });
26
+ $('#drop_html5, #drop_html5_scroll').on('drop', function(ev){
27
+ $(this).after('<div class="log">Drop with client position: ' + ev.clientX + ',' + ev.clientY)
28
+ if ($(this).hasClass('drop')) { ev.preventDefault(); }
29
+ });
19
30
  $('#drop_html5, #drop_html5_scroll').on('drop', function(ev){
20
31
  ev.preventDefault();
21
32
  var oev = ev.originalEvent;
@@ -36,6 +36,13 @@ Capybara::SpecHelper.spec '#all' do
36
36
  expect(@result).to include('Smith', 'John', 'John Smith')
37
37
  end
38
38
 
39
+ it 'should allow reversing the order' do
40
+ @session.visit('/form')
41
+ fields = @session.all(:fillable_field, 'Name', exact: false).to_a
42
+ reverse_fields = @session.all(:fillable_field, 'Name', order: :reverse, exact: false).to_a
43
+ expect(fields).to eq(reverse_fields.reverse)
44
+ end
45
+
39
46
  it 'should raise an error when given invalid options' do
40
47
  expect { @session.all('//p', schmoo: 'foo') }.to raise_error(ArgumentError)
41
48
  end
@@ -128,6 +135,7 @@ Capybara::SpecHelper.spec '#all' do
128
135
  it 'should succeed when the number of elements founds matches the expectation' do
129
136
  expect { @session.all(:css, 'h1, p', count: 4) }.not_to raise_error
130
137
  end
138
+
131
139
  it 'should raise ExpectationNotMet when the number of elements founds does not match the expectation' do
132
140
  expect { @session.all(:css, 'h1, p', count: 5) }.to raise_error(Capybara::ExpectationNotMet)
133
141
  end
@@ -137,6 +145,7 @@ Capybara::SpecHelper.spec '#all' do
137
145
  it 'should succeed when the number of elements founds matches the expectation' do
138
146
  expect { @session.all(:css, 'h1, p', minimum: 0) }.not_to raise_error
139
147
  end
148
+
140
149
  it 'should raise ExpectationNotMet when the number of elements founds does not match the expectation' do
141
150
  expect { @session.all(:css, 'h1, p', minimum: 5) }.to raise_error(Capybara::ExpectationNotMet)
142
151
  end
@@ -146,6 +155,7 @@ Capybara::SpecHelper.spec '#all' do
146
155
  it 'should succeed when the number of elements founds matches the expectation' do
147
156
  expect { @session.all(:css, 'h1, p', maximum: 4) }.not_to raise_error
148
157
  end
158
+
149
159
  it 'should raise ExpectationNotMet when the number of elements founds does not match the expectation' do
150
160
  expect { @session.all(:css, 'h1, p', maximum: 0) }.to raise_error(Capybara::ExpectationNotMet)
151
161
  end
@@ -155,6 +165,7 @@ Capybara::SpecHelper.spec '#all' do
155
165
  it 'should succeed when the number of elements founds matches the expectation' do
156
166
  expect { @session.all(:css, 'h1, p', between: 2..7) }.not_to raise_error
157
167
  end
168
+
158
169
  it 'should raise ExpectationNotMet when the number of elements founds does not match the expectation' do
159
170
  expect { @session.all(:css, 'h1, p', between: 0..3) }.to raise_error(Capybara::ExpectationNotMet)
160
171
  end
@@ -175,6 +186,7 @@ Capybara::SpecHelper.spec '#all' do
175
186
  between: 0..3 }
176
187
  expect { @session.all(:css, 'h1, p', o) }.not_to raise_error
177
188
  end
189
+
178
190
  context 'with no :count expectation' do
179
191
  it 'fails if :minimum is not met' do
180
192
  o = { minimum: 5,
@@ -182,18 +194,21 @@ Capybara::SpecHelper.spec '#all' do
182
194
  between: 2..7 }
183
195
  expect { @session.all(:css, 'h1, p', o) }.to raise_error(Capybara::ExpectationNotMet)
184
196
  end
197
+
185
198
  it 'fails if :maximum is not met' do
186
199
  o = { minimum: 0,
187
200
  maximum: 0,
188
201
  between: 2..7 }
189
202
  expect { @session.all(:css, 'h1, p', o) }.to raise_error(Capybara::ExpectationNotMet)
190
203
  end
204
+
191
205
  it 'fails if :between is not met' do
192
206
  o = { minimum: 0,
193
207
  maximum: 4,
194
208
  between: 0..3 }
195
209
  expect { @session.all(:css, 'h1, p', o) }.to raise_error(Capybara::ExpectationNotMet)
196
210
  end
211
+
197
212
  it 'succeeds if all combineable expectations are met' do
198
213
  o = { minimum: 0,
199
214
  maximum: 4,
@@ -20,6 +20,11 @@ Capybara::SpecHelper.spec '#ancestor' do
20
20
  expect(el.ancestor('//div', text: "Ancestor\nAncestor\nAncestor")[:id]).to eq('ancestor3')
21
21
  end
22
22
 
23
+ it 'should find the closest ancestor' do
24
+ el = @session.find(:css, '#child')
25
+ expect(el.ancestor('.//div', order: :reverse, match: :first)[:id]).to eq('ancestor1')
26
+ end
27
+
23
28
  it 'should raise an error if there are multiple matches' do
24
29
  el = @session.find(:css, '#child')
25
30
  expect { el.ancestor('//div') }.to raise_error(Capybara::Ambiguous)
@@ -159,6 +159,7 @@ Capybara::SpecHelper.spec '#assert_text' do
159
159
  between: 0..4 }
160
160
  expect { @session.assert_text('Header', o) }.not_to raise_error
161
161
  end
162
+
162
163
  context 'with no :count expectation' do
163
164
  it 'fails if :minimum is not met' do
164
165
  o = { minimum: 6,
@@ -166,18 +167,21 @@ Capybara::SpecHelper.spec '#assert_text' do
166
167
  between: 2..7 }
167
168
  expect { @session.assert_text('Header', o) }.to raise_error(Capybara::ExpectationNotMet)
168
169
  end
170
+
169
171
  it 'fails if :maximum is not met' do
170
172
  o = { minimum: 0,
171
173
  maximum: 0,
172
174
  between: 2..7 }
173
175
  expect { @session.assert_text('Header', o) }.to raise_error(Capybara::ExpectationNotMet)
174
176
  end
177
+
175
178
  it 'fails if :between is not met' do
176
179
  o = { minimum: 0,
177
180
  maximum: 5,
178
181
  between: 0..4 }
179
182
  expect { @session.assert_text('Header', o) }.to raise_error(Capybara::ExpectationNotMet)
180
183
  end
184
+
181
185
  it 'succeeds if all combineable expectations are met' do
182
186
  o = { minimum: 0,
183
187
  maximum: 5,
@@ -186,6 +186,11 @@ Capybara::SpecHelper.spec '#click_button' do
186
186
  @session.click_button(name: 'form[awesome]')
187
187
  expect(extract_results(@session)['first_name']).to eq('John')
188
188
  end
189
+
190
+ it 'should submit by specific button name regex' do
191
+ @session.click_button(name: /form\[awes.*\]/)
192
+ expect(extract_results(@session)['first_name']).to eq('John')
193
+ end
189
194
  end
190
195
 
191
196
  context 'with fields associated with the form using the form attribute', requires: [:form_attribute] do
@@ -275,14 +275,17 @@ Capybara::SpecHelper.spec '#find' do
275
275
  @session.find(:css, '.multiple', match: :one)
276
276
  end.to raise_error(Capybara::Ambiguous)
277
277
  end
278
+
278
279
  it 'raises an error even if there the match is exact and the others are inexact' do
279
280
  expect do
280
281
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singular')], exact: false, match: :one)
281
282
  end.to raise_error(Capybara::Ambiguous)
282
283
  end
284
+
283
285
  it 'returns the element if there is only one' do
284
286
  expect(@session.find(:css, '.singular', match: :one).text).to eq('singular')
285
287
  end
288
+
286
289
  it 'raises an error if there is no match' do
287
290
  expect do
288
291
  @session.find(:css, '.does-not-exist', match: :one)
@@ -294,6 +297,7 @@ Capybara::SpecHelper.spec '#find' do
294
297
  it 'returns the first matched element' do
295
298
  expect(@session.find(:css, '.multiple', match: :first).text).to eq('multiple one')
296
299
  end
300
+
297
301
  it 'raises an error if there is no match' do
298
302
  expect do
299
303
  @session.find(:css, '.does-not-exist', match: :first)
@@ -308,19 +312,23 @@ Capybara::SpecHelper.spec '#find' do
308
312
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('multiple')], match: :smart, exact: false)
309
313
  end.to raise_error(Capybara::Ambiguous)
310
314
  end
315
+
311
316
  it 'finds a single exact match when there also are inexact matches' do
312
317
  result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singular')], match: :smart, exact: false)
313
318
  expect(result.text).to eq('almost singular')
314
319
  end
320
+
315
321
  it 'raises an error when there are multiple inexact matches' do
316
322
  expect do
317
323
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singul')], match: :smart, exact: false)
318
324
  end.to raise_error(Capybara::Ambiguous)
319
325
  end
326
+
320
327
  it 'finds a single inexact match' do
321
328
  result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singular but')], match: :smart, exact: false)
322
329
  expect(result.text).to eq('almost singular but not quite')
323
330
  end
331
+
324
332
  it 'raises an error if there is no match' do
325
333
  expect do
326
334
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('does-not-exist')], match: :smart, exact: false)
@@ -334,20 +342,24 @@ Capybara::SpecHelper.spec '#find' do
334
342
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('multiple')], match: :smart, exact: true)
335
343
  end.to raise_error(Capybara::Ambiguous)
336
344
  end
345
+
337
346
  it 'finds a single exact match when there also are inexact matches' do
338
347
  result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singular')], match: :smart, exact: true)
339
348
  expect(result.text).to eq('almost singular')
340
349
  end
350
+
341
351
  it 'raises an error when there are multiple inexact matches' do
342
352
  expect do
343
353
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singul')], match: :smart, exact: true)
344
354
  end.to raise_error(Capybara::ElementNotFound)
345
355
  end
356
+
346
357
  it 'raises an error when there is a single inexact matches' do
347
358
  expect do
348
359
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singular but')], match: :smart, exact: true)
349
360
  end.to raise_error(Capybara::ElementNotFound)
350
361
  end
362
+
351
363
  it 'raises an error if there is no match' do
352
364
  expect do
353
365
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('does-not-exist')], match: :smart, exact: true)
@@ -362,18 +374,22 @@ Capybara::SpecHelper.spec '#find' do
362
374
  result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('multiple')], match: :prefer_exact, exact: false)
363
375
  expect(result.text).to eq('multiple one')
364
376
  end
377
+
365
378
  it 'finds a single exact match when there also are inexact matches' do
366
379
  result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singular')], match: :prefer_exact, exact: false)
367
380
  expect(result.text).to eq('almost singular')
368
381
  end
382
+
369
383
  it 'picks the first one when there are multiple inexact matches' do
370
384
  result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singul')], match: :prefer_exact, exact: false)
371
385
  expect(result.text).to eq('almost singular but not quite')
372
386
  end
387
+
373
388
  it 'finds a single inexact match' do
374
389
  result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singular but')], match: :prefer_exact, exact: false)
375
390
  expect(result.text).to eq('almost singular but not quite')
376
391
  end
392
+
377
393
  it 'raises an error if there is no match' do
378
394
  expect do
379
395
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('does-not-exist')], match: :prefer_exact, exact: false)
@@ -386,20 +402,24 @@ Capybara::SpecHelper.spec '#find' do
386
402
  result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('multiple')], match: :prefer_exact, exact: true)
387
403
  expect(result.text).to eq('multiple one')
388
404
  end
405
+
389
406
  it 'finds a single exact match when there also are inexact matches' do
390
407
  result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singular')], match: :prefer_exact, exact: true)
391
408
  expect(result.text).to eq('almost singular')
392
409
  end
410
+
393
411
  it 'raises an error if there are multiple inexact matches' do
394
412
  expect do
395
413
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singul')], match: :prefer_exact, exact: true)
396
414
  end.to raise_error(Capybara::ElementNotFound)
397
415
  end
416
+
398
417
  it 'raises an error if there is a single inexact match' do
399
418
  expect do
400
419
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('almost_singular but')], match: :prefer_exact, exact: true)
401
420
  end.to raise_error(Capybara::ElementNotFound)
402
421
  end
422
+
403
423
  it 'raises an error if there is no match' do
404
424
  expect do
405
425
  @session.find(:xpath, XPath.descendant[XPath.attr(:class).is('does-not-exist')], match: :prefer_exact, exact: true)
@@ -143,10 +143,56 @@ Capybara::SpecHelper.spec '#has_no_table?' do
143
143
  ])
144
144
  end
145
145
 
146
- it 'should consider columns' do
147
- expect(@session).to have_no_table('Vertical Headers', with_cols:
148
- [
149
- { 'First Name' => 'Joe' }
150
- ])
146
+ context 'using :with_cols' do
147
+ it 'should consider a single column' do
148
+ expect(@session).to have_no_table('Vertical Headers', with_cols:
149
+ [
150
+ { 'First Name' => 'Joe' }
151
+ ])
152
+ end
153
+
154
+ it 'should be true even if the last column does exist' do
155
+ expect(@session).to have_no_table('Vertical Headers', with_cols:
156
+ [
157
+ {
158
+ 'First Name' => 'What?',
159
+ 'What?' => 'Walpole',
160
+ 'City' => 'Oceanside' # This line makes the example fail
161
+ }
162
+ ])
163
+ end
164
+
165
+ it 'should be true if none of the columns exist' do
166
+ expect(@session).to have_no_table('Vertical Headers', with_cols:
167
+ [
168
+ {
169
+ 'First Name' => 'What?',
170
+ 'What?' => 'Walpole',
171
+ 'City' => 'What?'
172
+ }
173
+ ])
174
+ end
175
+
176
+ it 'should be true if the first column does match' do
177
+ expect(@session).to have_no_table('Vertical Headers', with_cols:
178
+ [
179
+ {
180
+ 'First Name' => 'Thomas',
181
+ 'Last Name' => 'What',
182
+ 'City' => 'What'
183
+ }
184
+ ])
185
+ end
186
+
187
+ it 'should be true if none of the columns match' do
188
+ expect(@session).to have_no_table('Vertical Headers', with_cols:
189
+ [
190
+ {
191
+ 'First Name' => 'What',
192
+ 'Last Name' => 'What',
193
+ 'City' => 'What'
194
+ }
195
+ ])
196
+ end
151
197
  end
152
198
  end
@@ -111,6 +111,37 @@ Capybara::SpecHelper.spec '#has_text?' do
111
111
  expect(@session).not_to have_text(/xxxxyzzz/)
112
112
  end
113
113
 
114
+ context 'with object implementing to_s and to_hash' do
115
+ it 'should work if the object is passed alone' do
116
+ with_to_hash = Class.new do
117
+ def to_s; '42' end
118
+ def to_hash; { value: 'Other hash' } end
119
+ end.new
120
+ @session.visit('/with_html')
121
+ expect(@session).to have_text(with_to_hash)
122
+ end
123
+
124
+ it 'should work if passed with empty options' do
125
+ with_to_hash = Class.new do
126
+ def to_s; '42' end
127
+ def to_hash; { value: 'Other hash' } end
128
+ end.new
129
+ @session.visit('/with_html')
130
+ expect(@session).to have_text(:visible, with_to_hash, {})
131
+ end
132
+
133
+ it 'should fail if passed without empty options' do
134
+ with_to_hash = Class.new do
135
+ def to_s; '42' end
136
+ def to_hash; { blah: 'Other hash' } end
137
+ end.new
138
+ @session.visit('/with_html')
139
+ expect do
140
+ expect(@session).to have_text(:visible, with_to_hash)
141
+ end.to raise_error(ArgumentError)
142
+ end
143
+ end
144
+
114
145
  context 'with exact: true option' do
115
146
  it 'should be true if text matches exactly' do
116
147
  @session.visit('/with_html')
@@ -493,6 +493,21 @@ Capybara::SpecHelper.spec 'node' do
493
493
  expect(@session).to have_css('div.log', text: /DragOver with client position: [1-9]\d*,[1-9]\d*/, count: 2)
494
494
  end
495
495
 
496
+ it 'should preserve clientX/Y from last dragover event' do
497
+ @session.visit('/with_js')
498
+ element = @session.find('//div[@id="drag_html5"]')
499
+ target = @session.find('//div[@id="drop_html5"]')
500
+ element.drag_to(target)
501
+
502
+ # The first "DragOver" div is inserted by the last dragover event dispatched
503
+ drag_over_div = @session.find('//div[@class="log" and starts-with(text(), "DragOver")]', match: :first)
504
+ position = drag_over_div.text.sub('DragOver ', '')
505
+
506
+ expect(@session).to have_css('div.log', text: /DragLeave #{position}/, count: 1)
507
+ expect(@session).to have_css('div.log', text: /Drop #{position}/, count: 1)
508
+ expect(@session).to have_css('div.log', text: /DragEnd #{position}/, count: 1)
509
+ end
510
+
496
511
  it 'should not HTML5 drag and drop on a non HTML5 drop element' do
497
512
  @session.visit('/with_js')
498
513
  element = @session.find('//div[@id="drag_html5"]')