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
@@ -1,16 +1,13 @@
1
1
  module Capybara
2
2
  class Query
3
- attr_accessor :selector, :locator, :options, :xpath, :find, :negative
3
+ attr_accessor :selector, :locator, :options, :expression, :find, :negative
4
4
 
5
- VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum]
5
+ VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum, :exact, :match, :wait]
6
+ VALID_MATCH = [:first, :smart, :prefer_exact, :one]
6
7
 
7
8
  def initialize(*args)
8
9
  @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
9
10
 
10
- unless options.has_key?(:visible)
11
- @options[:visible] = Capybara.ignore_hidden_elements
12
- end
13
-
14
11
  if args[0].is_a?(Symbol)
15
12
  @selector = Selector.all[args[0]]
16
13
  @locator = args[1]
@@ -20,8 +17,12 @@ module Capybara
20
17
  end
21
18
  @selector ||= Selector.all[Capybara.default_selector]
22
19
 
23
- @xpath = @selector.call(@locator).to_s
20
+ # for compatibility with Capybara 2.0
21
+ if Capybara.exact_options and @selector == Selector.all[:option]
22
+ @options[:exact] = true
23
+ end
24
24
 
25
+ @expression = @selector.call(@locator)
25
26
  assert_valid_keys!
26
27
  end
27
28
 
@@ -35,34 +36,76 @@ module Capybara
35
36
  end
36
37
 
37
38
  def matches_filters?(node)
38
- node.unsynchronized do
39
- if options[:text]
40
- regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text])
41
- return false if not node.text.match(regexp)
39
+ if options[:text]
40
+ regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
41
+ return false if not node.text(visible).match(regexp)
42
+ end
43
+ case visible
44
+ when :visible then return false unless node.visible?
45
+ when :hidden then return false if node.visible?
46
+ end
47
+ selector.custom_filters.each do |name, filter|
48
+ if options.has_key?(name)
49
+ return false unless filter.matches?(node, options[name])
50
+ elsif filter.default?
51
+ return false unless filter.matches?(node, filter.default)
42
52
  end
43
- return false if options[:visible] and not node.visible?
44
- selector.custom_filters.each do |name, block|
45
- return false if options.has_key?(name) and not block.call(node, options[name])
53
+ end
54
+ end
55
+
56
+ def visible
57
+ if options.has_key?(:visible)
58
+ case @options[:visible]
59
+ when true then :visible
60
+ when false then :all
61
+ else @options[:visible]
62
+ end
63
+ else
64
+ if Capybara.ignore_hidden_elements
65
+ :visible
66
+ else
67
+ :all
46
68
  end
47
- true
48
69
  end
49
70
  end
50
71
 
51
- def matches_count?(count)
52
- case
53
- when options[:between]
54
- options[:between] === count
55
- when options[:count]
56
- options[:count].to_i == count
57
- when options[:maximum]
58
- options[:maximum].to_i >= count
59
- when options[:minimum]
60
- options[:minimum].to_i <= count
72
+ def wait
73
+ if options.has_key?(:wait)
74
+ @options[:wait] or 0
61
75
  else
62
- count > 0
76
+ Capybara.default_wait_time
63
77
  end
64
78
  end
65
79
 
80
+ def exact?
81
+ if options.has_key?(:exact)
82
+ @options[:exact]
83
+ else
84
+ Capybara.exact
85
+ end
86
+ end
87
+
88
+ def match
89
+ if options.has_key?(:match)
90
+ @options[:match]
91
+ else
92
+ Capybara.match
93
+ end
94
+ end
95
+
96
+ def xpath(exact=nil)
97
+ exact = self.exact? if exact == nil
98
+ if @expression.respond_to?(:to_xpath) and exact
99
+ @expression.to_xpath(:exact)
100
+ else
101
+ @expression.to_s
102
+ end
103
+ end
104
+
105
+ def css
106
+ @expression
107
+ end
108
+
66
109
  private
67
110
 
68
111
  def assert_valid_keys!
@@ -73,6 +116,9 @@ module Capybara
73
116
  valid_names = valid_keys.map(&:inspect).join(", ")
74
117
  raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
75
118
  end
119
+ unless VALID_MATCH.include?(match)
120
+ raise ArgumentError, "invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(", ")}"
121
+ end
76
122
  end
77
123
  end
78
124
  end
@@ -80,8 +80,12 @@ class Capybara::RackTest::Browser
80
80
  @dom ||= Nokogiri::HTML(html)
81
81
  end
82
82
 
83
- def find(selector)
84
- dom.xpath(selector).map { |node| Capybara::RackTest::Node.new(self, node) }
83
+ def find(format, selector)
84
+ if format==:css
85
+ dom.css(selector, Capybara::RackTest::CSSHandlers.new)
86
+ else
87
+ dom.xpath(selector)
88
+ end.map { |node| Capybara::RackTest::Node.new(self, node) }
85
89
  end
86
90
 
87
91
  def html
@@ -89,7 +93,11 @@ class Capybara::RackTest::Browser
89
93
  rescue Rack::Test::Error
90
94
  ""
91
95
  end
92
-
96
+
97
+ def title
98
+ dom.xpath("//title").text
99
+ end
100
+
93
101
  protected
94
102
 
95
103
  def build_rack_mock_session
@@ -0,0 +1,8 @@
1
+ class Capybara::RackTest::CSSHandlers
2
+ def disabled list
3
+ list.find_all { |node| node.has_attribute? 'disabled' }
4
+ end
5
+ def enabled list
6
+ list.find_all { |node| !node.has_attribute? 'disabled' }
7
+ end
8
+ end
@@ -62,10 +62,14 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
62
62
  response.status
63
63
  end
64
64
 
65
- def find(selector)
66
- browser.find(selector)
65
+ def find_xpath(selector)
66
+ browser.find(:xpath, selector)
67
67
  end
68
-
68
+
69
+ def find_css(selector)
70
+ browser.find(:css,selector)
71
+ end
72
+
69
73
  def html
70
74
  browser.html
71
75
  end
@@ -73,6 +77,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
73
77
  def dom
74
78
  browser.dom
75
79
  end
80
+
81
+ def title
82
+ browser.title
83
+ end
76
84
 
77
85
  def reset!
78
86
  @browser = nil
@@ -17,7 +17,14 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
17
17
  def params(button)
18
18
  params = {}
19
19
 
20
- native.xpath("(.//input|.//select|.//textarea)[not(@disabled)]").map do |field|
20
+ form_element_types=[:input, :select, :textarea]
21
+ form_elements_xpath=XPath.generate do |x|
22
+ xpath=x.descendant(*form_element_types).where(~x.attr(:form))
23
+ xpath=xpath.union(x.anywhere(*form_element_types).where(x.attr(:form) == native[:id])) if native[:id]
24
+ xpath.where(~x.attr(:disabled))
25
+ end.to_s
26
+
27
+ native.xpath(form_elements_xpath).map do |field|
21
28
  case field.name
22
29
  when 'input'
23
30
  if %w(radio checkbox).include? field['type']
@@ -1,5 +1,9 @@
1
1
  class Capybara::RackTest::Node < Capybara::Driver::Node
2
- def text
2
+ def all_text
3
+ Capybara::Helpers.normalize_whitespace(native.text)
4
+ end
5
+
6
+ def visible_text
3
7
  Capybara::Helpers.normalize_whitespace(unnormalized_text)
4
8
  end
5
9
 
@@ -15,42 +19,21 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
15
19
  if (Array === value) && !self[:multiple]
16
20
  raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
17
21
  end
18
- if tag_name == 'input' and type == 'radio'
19
- other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
20
- driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
21
- native['checked'] = 'checked'
22
- elsif tag_name == 'input' and type == 'checkbox'
23
- if value && !native['checked']
24
- native['checked'] = 'checked'
25
- elsif !value && native['checked']
26
- native.remove_attribute('checked')
27
- end
28
- elsif tag_name == 'input'
29
- if (type == 'text' || type == 'password') && self[:maxlength] &&
30
- !self[:maxlength].empty?
31
- # Browser behavior for maxlength="0" is inconsistent, so we stick with
32
- # Firefox, allowing no input
33
- value = value[0...self[:maxlength].to_i]
34
- end
35
- if Array === value #Assert multiple attribute is present
36
- value.each do |v|
37
- new_native = native.clone
38
- new_native.remove_attribute('value')
39
- native.add_next_sibling(new_native)
40
- new_native['value'] = v.to_s
41
- end
42
- native.remove
43
- else
44
- native['value'] = value.to_s
45
- end
46
- elsif tag_name == "textarea"
47
- native.content = value.to_s
22
+
23
+ if radio?
24
+ set_radio(value)
25
+ elsif checkbox?
26
+ set_checkbox(value)
27
+ elsif input_field?
28
+ set_input(value)
29
+ elsif textarea?
30
+ native.content = value.to_s unless self[:readonly]
48
31
  end
49
32
  end
50
33
 
51
34
  def select_option
52
35
  if select_node['multiple'] != 'multiple'
53
- select_node.find(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
36
+ select_node.find_xpath(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
54
37
  end
55
38
  native["selected"] = 'selected'
56
39
  end
@@ -89,14 +72,26 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
89
72
  string_node.selected?
90
73
  end
91
74
 
75
+ def disabled?
76
+ if %w(option optgroup).include? tag_name
77
+ string_node.disabled? || find_xpath("parent::*")[0].disabled?
78
+ else
79
+ string_node.disabled?
80
+ end
81
+ end
82
+
92
83
  def path
93
84
  native.path
94
85
  end
95
86
 
96
- def find(locator)
87
+ def find_xpath(locator)
97
88
  native.xpath(locator).map { |n| self.class.new(driver, n) }
98
89
  end
99
90
 
91
+ def find_css(locator)
92
+ native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |n| self.class.new(driver, n) }
93
+ end
94
+
100
95
  def ==(other)
101
96
  native == other.native
102
97
  end
@@ -125,7 +120,7 @@ private
125
120
 
126
121
  # a reference to the select node if this is an option node
127
122
  def select_node
128
- find('./ancestor::select').first
123
+ find_xpath('./ancestor::select').first
129
124
  end
130
125
 
131
126
  def type
@@ -133,6 +128,67 @@ private
133
128
  end
134
129
 
135
130
  def form
136
- native.ancestors('form').first
131
+ if native[:form]
132
+ native.xpath("//form[@id='#{native[:form]}']").first
133
+ else
134
+ native.ancestors('form').first
135
+ end
136
+ end
137
+
138
+ def set_radio(value)
139
+ other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
140
+ driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
141
+ native['checked'] = 'checked'
142
+ end
143
+
144
+ def set_checkbox(value)
145
+ if value && !native['checked']
146
+ native['checked'] = 'checked'
147
+ elsif !value && native['checked']
148
+ native.remove_attribute('checked')
149
+ end
150
+ end
151
+
152
+ def set_input(value)
153
+ if text_or_password? && attribute_is_not_blank?(:maxlength)
154
+ # Browser behavior for maxlength="0" is inconsistent, so we stick with
155
+ # Firefox, allowing no input
156
+ value = value.to_s[0...self[:maxlength].to_i]
157
+ end
158
+ if Array === value #Assert multiple attribute is present
159
+ value.each do |v|
160
+ new_native = native.clone
161
+ new_native.remove_attribute('value')
162
+ native.add_next_sibling(new_native)
163
+ new_native['value'] = v.to_s
164
+ end
165
+ native.remove
166
+ else
167
+ native['value'] = value.to_s unless self[:readonly]
168
+ end
169
+ end
170
+
171
+ def attribute_is_not_blank?(attribute)
172
+ self[attribute] && !self[attribute].empty?
173
+ end
174
+
175
+ def checkbox?
176
+ input_field? && type == 'checkbox'
177
+ end
178
+
179
+ def input_field?
180
+ tag_name == 'input'
181
+ end
182
+
183
+ def radio?
184
+ input_field? && type == 'radio'
185
+ end
186
+
187
+ def textarea?
188
+ tag_name == "textarea"
189
+ end
190
+
191
+ def text_or_password?
192
+ input_field? && (type == 'text' || type == 'password')
137
193
  end
138
194
  end
@@ -12,7 +12,6 @@ Capybara.app = Rack::Builder.new do
12
12
  end
13
13
  end.to_app
14
14
 
15
- Capybara.asset_root = Rails.root.join('public')
16
15
  Capybara.save_and_open_page_path = Rails.root.join('tmp/capybara')
17
16
 
18
17
  # Override default rack_test driver to respect data-method attributes.
@@ -1,6 +1,23 @@
1
1
  require 'forwardable'
2
2
 
3
3
  module Capybara
4
+
5
+ ##
6
+ # A {Capybara::Result} represents a collection of {Capybara::Element} on the page. It is possible to interact with this
7
+ # collection similar to an Array because it implements Enumerable and offers the following Array methods through delegation:
8
+ #
9
+ # * []
10
+ # * each()
11
+ # * at()
12
+ # * size()
13
+ # * count()
14
+ # * length()
15
+ # * first()
16
+ # * last()
17
+ # * empty?()
18
+ #
19
+ # @see Capybara::Element
20
+ #
4
21
  class Result
5
22
  include Enumerable
6
23
  extend Forwardable
@@ -15,36 +32,13 @@ module Capybara
15
32
  def_delegators :@result, :each, :[], :at, :size, :count, :length, :first, :last, :empty?
16
33
 
17
34
  def matches_count?
18
- @query.matches_count?(@result.size)
19
- end
20
-
21
- def find!
22
- raise find_error if @result.size != 1
23
- @result.first
24
- end
25
-
26
- def find_error
27
- if @result.size == 0
28
- Capybara::ElementNotFound.new("Unable to find #{@query.description}")
29
- elsif @result.size > 1
30
- Capybara::Ambiguous.new("Ambiguous match, found #{size} elements matching #{@query.description}")
31
- end
35
+ Capybara::Helpers.matches_count?(@result.size, @query.options)
32
36
  end
33
37
 
34
38
  def failure_message
35
- message = if @query.options[:count]
36
- "expected #{@query.description} to be found #{@query.options[:count]} #{declension("time", "times", @query.options[:count])}"
37
- elsif @query.options[:between]
38
- "expected #{@query.description} to be found between #{@query.options[:between].first} and #{@query.options[:between].last} times"
39
- elsif @query.options[:maximum]
40
- "expected #{@query.description} to be found at most #{@query.options[:maximum]} #{declension("time", "times", @query.options[:maximum])}"
41
- elsif @query.options[:minimum]
42
- "expected #{@query.description} to be found at least #{@query.options[:minimum]} #{declension("time", "times", @query.options[:minimum])}"
43
- else
44
- "expected to find #{@query.description}"
45
- end
39
+ message = Capybara::Helpers.failure_message(@query.description, @query.options)
46
40
  if count > 0
47
- message << ", found #{count} #{declension("match", "matches")}: " << @result.map(&:text).map(&:inspect).join(", ")
41
+ message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << @result.map(&:text).map(&:inspect).join(", ")
48
42
  else
49
43
  message << " but there were no matches"
50
44
  end
@@ -56,17 +50,7 @@ module Capybara
56
50
  end
57
51
 
58
52
  def negative_failure_message
59
- "expected not to find #{@query.description}, but there #{declension("was", "were")} #{count} #{declension("match", "matches")}"
60
- end
61
-
62
- private
63
-
64
- def declension(singular, plural, count=count)
65
- if count == 1
66
- singular
67
- else
68
- plural
69
- end
53
+ failure_message.sub(/(to be found|to find)/, 'not \1')
70
54
  end
71
55
  end
72
56
  end
@@ -1,6 +1,16 @@
1
1
  module Capybara
2
2
  module RSpecMatchers
3
- class HaveSelector
3
+ class Matcher
4
+ def wrap(actual)
5
+ if actual.respond_to?("has_selector?")
6
+ actual
7
+ else
8
+ Capybara.string(actual.to_s)
9
+ end
10
+ end
11
+ end
12
+
13
+ class HaveSelector < Matcher
4
14
  def initialize(*args)
5
15
  @args = args
6
16
  end
@@ -17,59 +27,77 @@ module Capybara
17
27
  "have #{query.description}"
18
28
  end
19
29
 
20
- def wrap(actual)
21
- if actual.respond_to?("has_selector?")
22
- actual
23
- else
24
- Capybara.string(actual.to_s)
25
- end
26
- end
27
-
28
30
  def query
29
31
  @query ||= Capybara::Query.new(*@args)
30
32
  end
31
33
  end
32
34
 
33
- class HaveText
34
- attr_reader :text
35
+ class HaveText < Matcher
36
+ attr_reader :type, :content, :options
35
37
 
36
- def initialize(text)
37
- @text = text
38
+ def initialize(*args)
39
+ @type = args.shift if args.first.is_a?(Symbol)
40
+ @content = args.shift
41
+ @options = (args.first.is_a?(Hash))? args.first : {}
38
42
  end
39
43
 
40
44
  def matches?(actual)
41
45
  @actual = wrap(actual)
42
- @actual.has_text?(text)
46
+ @actual.has_text?(type, content, options)
43
47
  end
44
48
 
45
49
  def does_not_match?(actual)
46
50
  @actual = wrap(actual)
47
- @actual.has_no_text?(text)
51
+ @actual.has_no_text?(type, content, options)
48
52
  end
49
53
 
50
54
  def failure_message_for_should
51
- "expected there to be text #{format(text)} in #{format(@actual.text)}"
55
+ message = Capybara::Helpers.failure_message(description, options)
56
+ message << " in #{format(@actual.text(type))}"
57
+ message
52
58
  end
53
59
 
54
60
  def failure_message_for_should_not
55
- "expected there not to be text #{format(text)} in #{format(@actual.text)}"
61
+ failure_message_for_should.sub(/(to find)/, 'not \1')
56
62
  end
57
63
 
58
64
  def description
59
- "have text #{format(text)}"
65
+ "text #{format(content)}"
60
66
  end
61
67
 
62
- def wrap(actual)
63
- if actual.respond_to?("has_selector?")
64
- actual
65
- else
66
- Capybara.string(actual.to_s)
67
- end
68
+ def format(content)
69
+ content = Capybara::Helpers.normalize_whitespace(content) unless content.is_a? Regexp
70
+ content.inspect
71
+ end
72
+ end
73
+
74
+ class HaveTitle < Matcher
75
+ attr_reader :title
76
+
77
+ def initialize(title)
78
+ @title = title
68
79
  end
69
80
 
70
- def format(text)
71
- text = Capybara::Helpers.normalize_whitespace(text) unless text.is_a? Regexp
72
- text.inspect
81
+ def matches?(actual)
82
+ @actual = wrap(actual)
83
+ @actual.has_title?(title)
84
+ end
85
+
86
+ def does_not_match?(actual)
87
+ @actual = wrap(actual)
88
+ @actual.has_no_title?(title)
89
+ end
90
+
91
+ def failure_message_for_should
92
+ "expected there to be title #{title.inspect} in #{@actual.title.inspect}"
93
+ end
94
+
95
+ def failure_message_for_should_not
96
+ "expected there not to be title #{title.inspect} in #{@actual.title.inspect}"
97
+ end
98
+
99
+ def description
100
+ "have title #{title.inspect}"
73
101
  end
74
102
  end
75
103
 
@@ -85,12 +113,13 @@ module Capybara
85
113
  HaveSelector.new(:css, css, options)
86
114
  end
87
115
 
88
- def have_content(text)
89
- HaveText.new(text)
116
+ def have_text(*args)
117
+ HaveText.new(*args)
90
118
  end
119
+ alias_method :have_content, :have_text
91
120
 
92
- def have_text(text)
93
- HaveText.new(text)
121
+ def have_title(title)
122
+ HaveTitle.new(title)
94
123
  end
95
124
 
96
125
  def have_link(locator, options={})