capybara 2.0.3 → 2.1.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 (82) hide show
  1. data/History.md +82 -0
  2. data/README.md +57 -52
  3. data/lib/capybara/cucumber.rb +4 -1
  4. data/lib/capybara/driver/base.rb +13 -9
  5. data/lib/capybara/driver/node.rb +18 -6
  6. data/lib/capybara/helpers.rb +111 -22
  7. data/lib/capybara/node/actions.rb +30 -25
  8. data/lib/capybara/node/base.rb +25 -32
  9. data/lib/capybara/node/document.rb +6 -2
  10. data/lib/capybara/node/element.rb +44 -7
  11. data/lib/capybara/node/finders.rb +49 -22
  12. data/lib/capybara/node/matchers.rb +38 -21
  13. data/lib/capybara/node/simple.rb +31 -9
  14. data/lib/capybara/query.rb +72 -26
  15. data/lib/capybara/rack_test/browser.rb +11 -3
  16. data/lib/capybara/rack_test/css_handlers.rb +8 -0
  17. data/lib/capybara/rack_test/driver.rb +11 -3
  18. data/lib/capybara/rack_test/form.rb +8 -1
  19. data/lib/capybara/rack_test/node.rb +91 -35
  20. data/lib/capybara/rails.rb +0 -1
  21. data/lib/capybara/result.rb +21 -37
  22. data/lib/capybara/rspec/matchers.rb +60 -31
  23. data/lib/capybara/selector.rb +46 -13
  24. data/lib/capybara/selenium/driver.rb +42 -7
  25. data/lib/capybara/selenium/node.rb +23 -5
  26. data/lib/capybara/server.rb +1 -1
  27. data/lib/capybara/session.rb +47 -12
  28. data/lib/capybara/spec/public/test.js +12 -0
  29. data/lib/capybara/spec/session/all_spec.rb +9 -8
  30. data/lib/capybara/spec/session/attach_file_spec.rb +14 -0
  31. data/lib/capybara/spec/session/check_spec.rb +14 -0
  32. data/lib/capybara/spec/session/choose_spec.rb +14 -0
  33. data/lib/capybara/spec/session/click_button_spec.rb +77 -1
  34. data/lib/capybara/spec/session/click_link_or_button_spec.rb +65 -0
  35. data/lib/capybara/spec/session/click_link_spec.rb +24 -0
  36. data/lib/capybara/spec/session/current_scope_spec.rb +29 -0
  37. data/lib/capybara/spec/session/fill_in_spec.rb +26 -0
  38. data/lib/capybara/spec/session/find_button_spec.rb +12 -0
  39. data/lib/capybara/spec/session/find_by_id_spec.rb +12 -1
  40. data/lib/capybara/spec/session/find_field_spec.rb +30 -0
  41. data/lib/capybara/spec/session/find_link_spec.rb +12 -0
  42. data/lib/capybara/spec/session/find_spec.rb +264 -16
  43. data/lib/capybara/spec/session/first_spec.rb +25 -8
  44. data/lib/capybara/spec/session/has_css_spec.rb +2 -2
  45. data/lib/capybara/spec/session/has_field_spec.rb +10 -2
  46. data/lib/capybara/spec/session/has_selector_spec.rb +9 -0
  47. data/lib/capybara/spec/session/has_text_spec.rb +96 -0
  48. data/lib/capybara/spec/session/has_title_spec.rb +47 -0
  49. data/lib/capybara/spec/session/node_spec.rb +48 -0
  50. data/lib/capybara/spec/session/reset_session_spec.rb +10 -2
  51. data/lib/capybara/spec/session/save_page_spec.rb +30 -0
  52. data/lib/capybara/spec/session/select_spec.rb +65 -0
  53. data/lib/capybara/spec/session/text_spec.rb +31 -0
  54. data/lib/capybara/spec/session/title_spec.rb +16 -0
  55. data/lib/capybara/spec/session/uncheck_spec.rb +14 -0
  56. data/lib/capybara/spec/session/unselect_spec.rb +42 -0
  57. data/lib/capybara/spec/session/visit_spec.rb +3 -3
  58. data/lib/capybara/spec/session/within_frame_spec.rb +14 -0
  59. data/lib/capybara/spec/session/within_spec.rb +3 -3
  60. data/lib/capybara/spec/spec_helper.rb +7 -2
  61. data/lib/capybara/spec/views/form.erb +39 -2
  62. data/lib/capybara/spec/views/frame_child.erb +9 -0
  63. data/lib/capybara/spec/views/frame_parent.erb +8 -0
  64. data/lib/capybara/spec/views/with_base_tag.erb +10 -0
  65. data/lib/capybara/spec/views/with_count.erb +7 -0
  66. data/lib/capybara/spec/views/with_hover.erb +17 -0
  67. data/lib/capybara/spec/views/with_html.erb +23 -2
  68. data/lib/capybara/spec/views/with_js.erb +5 -0
  69. data/lib/capybara/spec/views/with_scope.erb +6 -1
  70. data/lib/capybara/spec/views/with_title.erb +1 -0
  71. data/lib/capybara/spec/views/within_frames.erb +1 -0
  72. data/lib/capybara/version.rb +1 -1
  73. data/lib/capybara.rb +43 -62
  74. data/spec/basic_node_spec.rb +75 -24
  75. data/spec/dsl_spec.rb +2 -1
  76. data/spec/rack_test_spec.rb +4 -3
  77. data/spec/rspec/matchers_spec.rb +105 -17
  78. data/spec/server_spec.rb +15 -8
  79. data/spec/spec_helper.rb +2 -1
  80. data.tar.gz.sig +0 -0
  81. metadata +82 -22
  82. metadata.gz.sig +0 -0
data/History.md CHANGED
@@ -1,3 +1,85 @@
1
+ # Version 2.1.0
2
+
3
+ Release date: Unreleased
4
+
5
+ ### Changed
6
+
7
+ * Hard version requirement on Ruby >= 1.9.3. Capybara will no longer install
8
+ on 1.8.7. [Felix Schäfer]
9
+ * Capybara no longer depends on the `selenium-webdriver` gem. Add it to
10
+ your Gemfile if you wish to use the Selenium driver. [Jonas Nicklas]
11
+ * `Capybara.ignore_hidden_elements` defaults to `true`. [Jonas Nicklas]
12
+ * In case of multiple matches `smart` matching is used by default. Set
13
+ `Capybara.match = :one` to revert to old behaviour. [Jonas Nicklas].
14
+ * Options in select boxes use smart matching and no longer need to match
15
+ exactly. Set `Capybara.exact_options = false` to revert to old behaviour.
16
+ [Jonas Nicklas].
17
+ * Visibility of text depends on `Capybara.ignore_hidden_elements` instead of
18
+ always returning only visible text. Set `Capybara.visible_text_only = true`
19
+ to revert to old behaviour. [Jonas Nicklas]
20
+ * Cucumber cleans up session after scenario instead. This is consistent with
21
+ RSpec and makes more sense, since we raise server errors in `reset!`.
22
+ [Jonas Nicklas]
23
+
24
+ ### Added
25
+
26
+ * All actions (`click_link`, `fill_in`, etc...) and finders now take an options
27
+ hash, which is passed through to `find`. [Jonas Nicklas]
28
+ * CSS selectors are sent straight through to driver instead of being converted
29
+ to XPath first. Enables the use of some pseudo selectors, such as `invalid`
30
+ in some drivers. [Thomas Walpole]
31
+ * `Capybara.asset_host` option, which inserts a `base` tag into the page on
32
+ `save_and_open_page`, eases debugging with the Rails asset pipeline.
33
+ [Steve Hull]
34
+ * `exact` option, can specify whether to match substrings or entire text.
35
+ [Jonas Nicklas]
36
+ * `match` option, can specify behaviour in case of multiple matches.
37
+ [Jonas Nicklas]
38
+ * `wait` option, can specify how long to wait for a given action/finder.
39
+ [Jonas Nicklas]
40
+ * Config option which disables bubbling of errors raised inside server.
41
+ [Jonas Nicklas]
42
+ * `text` now takes a parameter which makes it possible to return either all
43
+ text or only visible text. The default depends on
44
+ `Capybara.ignore_hidden_elements`. `Capybara.visible_text_only` option is
45
+ available for compatibility. [Jonas Nicklas]
46
+ * `has_content?` and `has_text?` now take the same count options as `has_selector?`
47
+ [Andrey Botalov]
48
+ * `current_scope` is now public API, returns the current element when `within`
49
+ is used. [Martijn Walraven]
50
+ * `find("input").disabled?` returns true if a node is disabled. [Ben Lovell]
51
+ * Find disabled fields and buttons with `:disabled => false`. [Jonas Nicklas]
52
+ * `find("input").hover` moves the mouse to the element in supported drivers.
53
+ [Thomas Walpole]
54
+ * RackTest driver now support `form` attribute on form elements.
55
+ [Thomas Walpole]
56
+ * `page.title` returns the page title. [Terry Progetto]
57
+ * `has_title?` matcher to assert on page title. [Jonas Nicklas]
58
+ * The gem is now signed with a certicficate. The public key is available in the
59
+ repo. [Jonas Nicklas]
60
+ * `:select` and `:textarea` are valid options for the `:type` filter on `find_field`
61
+ and `has_field?`. [Yann Plancqueel]
62
+
63
+ ### Fixed
64
+
65
+ * Fixed race conditions when synchronizing across multiple nodes [Jonas Nicklas]
66
+ * Fixed race conditions in deeply nested selectors [Jonas Nicklas]
67
+ * Fix issue with `within_frame`, where selecting multiple nested frames didn't
68
+ work as intended. [Thomas Walpole]
69
+ * RackTest no longer fills in readonly textareas. [Thomas Walpole]
70
+ * Don't use autoload to load files, require them directly instead. [Jonas Nicklas]
71
+ * Rescue weird exceptions when booting server [John Wilger]
72
+ * Non strings are now properly cast when using the maxlength attribute [Jonas Nicklas]
73
+
74
+ # Version 2.0.3
75
+
76
+ Release date: 2013-03-26
77
+
78
+ * Check against Rails version fixed to work with Rails' master branch now returning
79
+ a Gem::Version [Jonas Nicklas]
80
+ * Use posix character class for whitespace replace, solves various encoding
81
+ problems on Ruby 2.0.0 and JRuby. [Ben Cates]
82
+
1
83
  # Version 2.0.2
2
84
 
3
85
  Release date: 2012-12-31
data/README.md CHANGED
@@ -23,19 +23,19 @@ GitHub): http://groups.google.com/group/ruby-capybara
23
23
 
24
24
  ## Setup
25
25
 
26
- To install, type
26
+ Capybara requires Ruby 1.9.3 or later. To install, type:
27
27
 
28
28
  ```bash
29
- sudo gem install capybara
29
+ gem install capybara
30
30
  ```
31
31
 
32
- If you are using Rails, add this line to your test helper file:
32
+ If the application that you are testing is a Rails app, add this line to your test helper file:
33
33
 
34
34
  ```ruby
35
35
  require 'capybara/rails'
36
36
  ```
37
37
 
38
- If you are not using Rails, set Capybara.app to your rack app:
38
+ If the application that you are testing is a Rack app, but not Rails, set Capybara.app to your Rack app:
39
39
 
40
40
  ```ruby
41
41
  Capybara.app = MyRackApp
@@ -102,11 +102,13 @@ describe "the signup process", :type => :feature do
102
102
  end
103
103
 
104
104
  it "signs me in" do
105
+ visit '/sessions/new'
105
106
  within("#session") do
106
107
  fill_in 'Login', :with => 'user@example.com'
107
108
  fill_in 'Password', :with => 'password'
108
109
  end
109
110
  click_link 'Sign in'
111
+ page.should have_content 'Success'
110
112
  end
111
113
  end
112
114
  ```
@@ -131,21 +133,25 @@ feature "Signing up" do
131
133
  end
132
134
 
133
135
  scenario "Signing in with correct credentials" do
136
+ visit '/sessions/new'
134
137
  within("#session") do
135
138
  fill_in 'Login', :with => 'user@example.com'
136
139
  fill_in 'Password', :with => 'caplin'
137
140
  end
138
141
  click_link 'Sign in'
142
+ page.should have_content 'Success'
139
143
  end
140
144
 
141
145
  given(:other_user) { User.make(:email => 'other@example.com', :password => 'rous') }
142
146
 
143
147
  scenario "Signing in as another user" do
148
+ visit '/sessions/new'
144
149
  within("#session") do
145
150
  fill_in 'Login', :with => other_user.email
146
151
  fill_in 'Password', :with => other_user.password
147
152
  end
148
153
  click_link 'Sign in'
154
+ page.should have_content 'Invalid email or password'
149
155
  end
150
156
  end
151
157
  ```
@@ -156,36 +162,14 @@ end
156
162
 
157
163
  ## Using Capybara with Test::Unit
158
164
 
159
- * If you are using Rails, add `database_cleaner` to your Gemfile:
160
-
161
- ```ruby
162
- group :test do
163
- gem 'database_cleaner'
164
- end
165
- ```
166
-
167
- Then add the following code in your `test_helper.rb` file to make
168
- Capybara available in all test cases deriving from
165
+ * If you are using Rails, add the following code in your `test_helper.rb`
166
+ file to make Capybara available in all test cases deriving from
169
167
  `ActionDispatch::IntegrationTest`:
170
168
 
171
169
  ```ruby
172
- # Transactional fixtures do not work with Selenium tests, because Capybara
173
- # uses a separate server thread, which the transactions would be hidden
174
- # from. We hence use DatabaseCleaner to truncate our test database.
175
- DatabaseCleaner.strategy = :truncation
176
-
177
170
  class ActionDispatch::IntegrationTest
178
171
  # Make the Capybara DSL available in all integration tests
179
172
  include Capybara::DSL
180
-
181
- # Stop ActiveRecord from wrapping tests in transactions
182
- self.use_transactional_fixtures = false
183
-
184
- teardown do
185
- DatabaseCleaner.clean # Truncate the database
186
- Capybara.reset_sessions! # Forget the (simulated) browser state
187
- Capybara.use_default_driver # Revert Capybara.current_driver to Capybara.default_driver
188
- end
189
173
  end
190
174
  ```
191
175
 
@@ -401,7 +385,6 @@ certain elements, and working with and manipulating those elements.
401
385
  ```ruby
402
386
  page.has_selector?('table tr')
403
387
  page.has_selector?(:xpath, '//table/tr')
404
- page.has_no_selector?(:content)
405
388
 
406
389
  page.has_xpath?('//table/tr')
407
390
  page.has_css?('table tr.foo')
@@ -416,7 +399,6 @@ You can use these with RSpec's magic matchers:
416
399
  ```ruby
417
400
  page.should have_selector('table tr')
418
401
  page.should have_selector(:xpath, '//table/tr')
419
- page.should have_no_selector(:content)
420
402
 
421
403
  page.should have_xpath('//table/tr')
422
404
  page.should have_css('table tr.foo')
@@ -524,6 +506,49 @@ Finally, in drivers that support it, you can save a screenshot:
524
506
  page.save_screenshot('screenshot.png')
525
507
  ```
526
508
 
509
+ ## Matching
510
+
511
+ It is possible to customize how Capybara finds elements. At your disposal
512
+ are two options, `Capybara.exact` and `Capybara.match`.
513
+
514
+ ### Exactness
515
+
516
+ `Capybara.exact` and the `exact` option work together with the `is` expression
517
+ inside the XPath gem. When `exact` is true, all `is` expressions match exactly,
518
+ when it is false, they allow substring matches. Many of the selectors built into
519
+ Capybara use the `is` expression. This way you can specify whether you want to
520
+ allow substring matches or not. `Capybara.exact` is false by default.
521
+
522
+ For example:
523
+
524
+ ```ruby
525
+ click_link("Password") # also matches "Password confirmation"
526
+ Capybara.exact = true
527
+ click_link("Password") # does not match "Password confirmation"
528
+ click_link("Password", exact: false) # can be overridden
529
+ ```
530
+
531
+ ### Strategy
532
+
533
+ Using `Capybara.match` and the equivalent `match` option, you can control how
534
+ Capybara behaves when multiple elements all match a query. There are currently
535
+ four different strategies built into Capybara:
536
+
537
+ 1. **first:** Just picks the first element that matches.
538
+ 2. **one:** Raises an error if more than one element matches.
539
+ 3. **smart:** If `exact` is `true`, raises an error if more than one element
540
+ matches, just like `one`. If `exact` is `false`, it will first try to find
541
+ an exact match. An error is raised if more than one element is found. If no
542
+ element is found, a new search is performed which allows partial matches. If
543
+ that search returns multiple matches, an error is raised.
544
+ 4. **prefer_exact:** If multiple matches are found, some of which are exact,
545
+ and some of which are not, then the first exactly matching element is
546
+ returned.
547
+
548
+ The default for `Capybara.match` is `:smart`. To emulate the behaviour in
549
+ Capybara 2.0.x, set `Capybara.match` to `:one`. To emulate the behaviour in
550
+ Capybara 1.x, set `Capybara.match` to `:prefer_exact`.
551
+
527
552
  ## Transactions and database setup
528
553
 
529
554
  Some Capybara drivers need to run against an actual HTTP server. Capybara takes
@@ -575,7 +600,7 @@ page.should have_content('baz')
575
600
  If clicking on the *foo* link triggers an asynchronous process, such as
576
601
  an Ajax request, which, when complete will add the *bar* link to the page,
577
602
  clicking on the *bar* link would be expected to fail, since that link doesn't
578
- exist yet. However Capybara is smart enought to retry finding the link for a
603
+ exist yet. However Capybara is smart enough to retry finding the link for a
579
604
  brief period of time before giving up and throwing an error. The same is true of
580
605
  the next line, which looks for the content *baz* on the page; it will retry
581
606
  looking for that content for a brief time. You can adjust how long this period
@@ -648,7 +673,7 @@ This enables its use in unsupported testing frameworks, and for general-purpose
648
673
  ## Calling remote servers
649
674
 
650
675
  Normally Capybara expects to be testing an in-process Rack application, but you
651
- can also use it to talk to a web server running anywhere on the internets, by
676
+ can also use it to talk to a web server running anywhere on the internet, by
652
677
  setting app_host:
653
678
 
654
679
  ```ruby
@@ -737,25 +762,6 @@ find(:row, 3)
737
762
  find(:flash_type, :notice)
738
763
  ```
739
764
 
740
- You can specify an optional match option which will automatically use the
741
- selector if it matches the argument:
742
-
743
- ```ruby
744
- Capybara.add_selector(:id) do
745
- xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
746
- match { |value| value.is_a?(Symbol) }
747
- end
748
- ```
749
-
750
- Now use it like this:
751
-
752
- ```ruby
753
- find(:post_123)
754
- ```
755
-
756
- This :id selector is already built into Capybara by default, so you don't
757
- need to add it yourself.
758
-
759
765
  ## Beware the XPath // trap
760
766
 
761
767
  In XPath the expression // means something very specific, and it might not be what
@@ -840,7 +846,6 @@ additional info about how the underlying driver can be configured.
840
846
  To set up a development environment, simply do:
841
847
 
842
848
  ```bash
843
- git submodule update --init
844
849
  bundle install
845
850
  bundle exec rake # run the test suite
846
851
  ```
@@ -6,8 +6,11 @@ require 'capybara/rspec/matchers'
6
6
  World(Capybara::DSL)
7
7
  World(Capybara::RSpecMatchers)
8
8
 
9
- Before do
9
+ After do
10
10
  Capybara.reset_sessions!
11
+ end
12
+
13
+ Before do
11
14
  Capybara.use_default_driver
12
15
  end
13
16
 
@@ -7,7 +7,11 @@ class Capybara::Driver::Base
7
7
  raise NotImplementedError
8
8
  end
9
9
 
10
- def find(query)
10
+ def find_xpath(query)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def find_css(query)
11
15
  raise NotImplementedError
12
16
  end
13
17
 
@@ -16,31 +20,31 @@ class Capybara::Driver::Base
16
20
  end
17
21
 
18
22
  def execute_script(script)
19
- raise Capybara::NotSupportedByDriverError
23
+ raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#execute_script'
20
24
  end
21
25
 
22
26
  def evaluate_script(script)
23
- raise Capybara::NotSupportedByDriverError
27
+ raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#evaluate_script'
24
28
  end
25
29
 
26
30
  def save_screenshot(path, options={})
27
- raise Capybara::NotSupportedByDriverError
31
+ raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#save_screenshot'
28
32
  end
29
33
 
30
34
  def response_headers
31
- raise Capybara::NotSupportedByDriverError
35
+ raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#response_headers'
32
36
  end
33
37
 
34
38
  def status_code
35
- raise Capybara::NotSupportedByDriverError
39
+ raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#status_code'
36
40
  end
37
41
 
38
- def within_frame(frame_id)
39
- raise Capybara::NotSupportedByDriverError
42
+ def within_frame(frame_handle)
43
+ raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#within_frame'
40
44
  end
41
45
 
42
46
  def within_window(handle)
43
- raise Capybara::NotSupportedByDriverError
47
+ raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#within_window'
44
48
  end
45
49
 
46
50
  def invalid_element_errors
@@ -8,7 +8,11 @@ module Capybara
8
8
  @native = native
9
9
  end
10
10
 
11
- def text
11
+ def all_text
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def visible_text
12
16
  raise NotImplementedError
13
17
  end
14
18
 
@@ -37,6 +41,10 @@ module Capybara
37
41
  raise NotImplementedError
38
42
  end
39
43
 
44
+ def hover
45
+ raise NotImplementedError
46
+ end
47
+
40
48
  def drag_to(element)
41
49
  raise NotImplementedError
42
50
  end
@@ -57,22 +65,26 @@ module Capybara
57
65
  raise NotImplementedError
58
66
  end
59
67
 
68
+ def disabled?
69
+ raise NotImplementedError
70
+ end
71
+
60
72
  def path
61
- raise NotSupportedByDriverError
73
+ raise NotSupportedByDriverError, 'Capybara::Driver::Node#path'
62
74
  end
63
-
75
+
64
76
  def trigger(event)
65
- raise NotSupportedByDriverError
77
+ raise NotSupportedByDriverError, 'Capybara::Driver::Node#trigger'
66
78
  end
67
79
 
68
80
  def inspect
69
81
  %(#<#{self.class} tag="#{tag_name}" path="#{path}">)
70
- rescue NotSupportedByDriverError
82
+ rescue NotSupportedByDriverError, 'Capybara::Driver::Node#inspect'
71
83
  %(#<#{self.class} tag="#{tag_name}">)
72
84
  end
73
85
 
74
86
  def ==(other)
75
- raise NotSupportedByDriverError
87
+ raise NotSupportedByDriverError, 'Capybara::Driver::Node#=='
76
88
  end
77
89
  end
78
90
  end
@@ -1,31 +1,120 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Capybara
4
+
5
+ # @api private
4
6
  module Helpers
5
- class << self
6
- ##
7
- #
8
- # Normalizes whitespace space by stripping leading and trailing
9
- # whitespace and replacing sequences of whitespace characters
10
- # with a single space.
11
- #
12
- # @param [String] text Text to normalize
13
- # @return [String] Normalized text
14
- #
15
- def normalize_whitespace(text)
16
- text.to_s.gsub(/[[:space:]]+/, ' ').strip
7
+ extend self
8
+
9
+ ##
10
+ #
11
+ # Normalizes whitespace space by stripping leading and trailing
12
+ # whitespace and replacing sequences of whitespace characters
13
+ # with a single space.
14
+ #
15
+ # @param [String] text Text to normalize
16
+ # @return [String] Normalized text
17
+ #
18
+ def normalize_whitespace(text)
19
+ text.to_s.gsub(/[[:space:]]+/, ' ').strip
20
+ end
21
+
22
+ ##
23
+ #
24
+ # Escapes any characters that would have special meaning in a regexp
25
+ # if text is not a regexp
26
+ #
27
+ # @param [String] text Text to escape
28
+ # @return [String] Escaped text
29
+ #
30
+ def to_regexp(text)
31
+ text.is_a?(Regexp) ? text : Regexp.new(Regexp.escape(normalize_whitespace(text)))
32
+ end
33
+
34
+ ##
35
+ #
36
+ # Injects a `<base>` tag into the given HTML code, pointing to
37
+ # `Capybara.asset_host`.
38
+ #
39
+ # @param [String] html HTML code to inject into
40
+ # @return [String] The modified HTML code
41
+ #
42
+ def inject_asset_host(html)
43
+ if Capybara.asset_host
44
+ if Nokogiri::HTML(html).css("base").empty? and match = html.match(/<head[^<]*?>/)
45
+ html.clone.insert match.end(0), "<base href='#{Capybara.asset_host}' />"
46
+ end
47
+ else
48
+ html
17
49
  end
50
+ end
51
+
52
+ ##
53
+ #
54
+ # Checks if the given count matches the given count options. By default,
55
+ # when no options are given, count should be larger than zero.
56
+ #
57
+ # @param [Integer] count The actual number. Should be coercible via Integer()
58
+ # @option [Range] between Count must be within the given range
59
+ # @option [Integer] count Count must be exactly this
60
+ # @option [Integer] maximum Count must be smaller than or equal to this value
61
+ # @option [Integer] minimum Count must be larger than or equal to this value
62
+ #
63
+ def matches_count?(count, options={})
64
+ case
65
+ when options[:between]
66
+ options[:between] === count
67
+ when options[:count]
68
+ Integer(options[:count]) == count
69
+ when options[:maximum]
70
+ Integer(options[:maximum]) >= count
71
+ when options[:minimum]
72
+ Integer(options[:minimum]) <= count
73
+ else
74
+ count > 0
75
+ end
76
+ end
77
+
78
+ ##
79
+ #
80
+ # Generates a failure message given a description of the query and count
81
+ # options.
82
+ #
83
+ # @param [String] description Description of a query
84
+ # @option [Range] between Count should have been within the given range
85
+ # @option [Integer] count Count should have been exactly this
86
+ # @option [Integer] maximum Count should have been smaller than or equal to this value
87
+ # @option [Integer] minimum Count should have been larger than or equal to this value
88
+ #
89
+ def failure_message(description, options={})
90
+ message = "expected to find #{description}"
91
+ if options[:count]
92
+ message << " #{options[:count]} #{declension('time', 'times', options[:count])}"
93
+ elsif options[:between]
94
+ message << " between #{options[:between].first} and #{options[:between].last} times"
95
+ elsif options[:maximum]
96
+ message << " at most #{options[:maximum]} #{declension('time', 'times', options[:maximum])}"
97
+ elsif options[:minimum]
98
+ message << " at least #{options[:minimum]} #{declension('time', 'times', options[:minimum])}"
99
+ end
100
+ message
101
+ end
18
102
 
19
- ##
20
- #
21
- # Escapes any characters that would have special meaning in a regexp
22
- # if text is not a regexp
23
- #
24
- # @param [String] text Text to escape
25
- # @return [String] Escaped text
26
- #
27
- def to_regexp(text)
28
- text.is_a?(Regexp) ? text : Regexp.escape(normalize_whitespace(text))
103
+ ##
104
+ #
105
+ # A poor man's `pluralize`. Given two declensions, one singular and one
106
+ # plural, as well as a count, this will pick the correct declension. This
107
+ # way we can generate gramatically correct error message.
108
+ #
109
+ # @param [String] singular The singular form of the word
110
+ # @param [String] plural The plural form of the word
111
+ # @param [Integer] count The number of items
112
+ #
113
+ def declension(singular, plural, count)
114
+ if count == 1
115
+ singular
116
+ else
117
+ plural
29
118
  end
30
119
  end
31
120
  end