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.
- data/History.md +82 -0
- data/README.md +57 -52
- data/lib/capybara/cucumber.rb +4 -1
- data/lib/capybara/driver/base.rb +13 -9
- data/lib/capybara/driver/node.rb +18 -6
- data/lib/capybara/helpers.rb +111 -22
- data/lib/capybara/node/actions.rb +30 -25
- data/lib/capybara/node/base.rb +25 -32
- data/lib/capybara/node/document.rb +6 -2
- data/lib/capybara/node/element.rb +44 -7
- data/lib/capybara/node/finders.rb +49 -22
- data/lib/capybara/node/matchers.rb +38 -21
- data/lib/capybara/node/simple.rb +31 -9
- data/lib/capybara/query.rb +72 -26
- data/lib/capybara/rack_test/browser.rb +11 -3
- data/lib/capybara/rack_test/css_handlers.rb +8 -0
- data/lib/capybara/rack_test/driver.rb +11 -3
- data/lib/capybara/rack_test/form.rb +8 -1
- data/lib/capybara/rack_test/node.rb +91 -35
- data/lib/capybara/rails.rb +0 -1
- data/lib/capybara/result.rb +21 -37
- data/lib/capybara/rspec/matchers.rb +60 -31
- data/lib/capybara/selector.rb +46 -13
- data/lib/capybara/selenium/driver.rb +42 -7
- data/lib/capybara/selenium/node.rb +23 -5
- data/lib/capybara/server.rb +1 -1
- data/lib/capybara/session.rb +47 -12
- data/lib/capybara/spec/public/test.js +12 -0
- data/lib/capybara/spec/session/all_spec.rb +9 -8
- data/lib/capybara/spec/session/attach_file_spec.rb +14 -0
- data/lib/capybara/spec/session/check_spec.rb +14 -0
- data/lib/capybara/spec/session/choose_spec.rb +14 -0
- data/lib/capybara/spec/session/click_button_spec.rb +77 -1
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +65 -0
- data/lib/capybara/spec/session/click_link_spec.rb +24 -0
- data/lib/capybara/spec/session/current_scope_spec.rb +29 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +26 -0
- data/lib/capybara/spec/session/find_button_spec.rb +12 -0
- data/lib/capybara/spec/session/find_by_id_spec.rb +12 -1
- data/lib/capybara/spec/session/find_field_spec.rb +30 -0
- data/lib/capybara/spec/session/find_link_spec.rb +12 -0
- data/lib/capybara/spec/session/find_spec.rb +264 -16
- data/lib/capybara/spec/session/first_spec.rb +25 -8
- data/lib/capybara/spec/session/has_css_spec.rb +2 -2
- data/lib/capybara/spec/session/has_field_spec.rb +10 -2
- data/lib/capybara/spec/session/has_selector_spec.rb +9 -0
- data/lib/capybara/spec/session/has_text_spec.rb +96 -0
- data/lib/capybara/spec/session/has_title_spec.rb +47 -0
- data/lib/capybara/spec/session/node_spec.rb +48 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +10 -2
- data/lib/capybara/spec/session/save_page_spec.rb +30 -0
- data/lib/capybara/spec/session/select_spec.rb +65 -0
- data/lib/capybara/spec/session/text_spec.rb +31 -0
- data/lib/capybara/spec/session/title_spec.rb +16 -0
- data/lib/capybara/spec/session/uncheck_spec.rb +14 -0
- data/lib/capybara/spec/session/unselect_spec.rb +42 -0
- data/lib/capybara/spec/session/visit_spec.rb +3 -3
- data/lib/capybara/spec/session/within_frame_spec.rb +14 -0
- data/lib/capybara/spec/session/within_spec.rb +3 -3
- data/lib/capybara/spec/spec_helper.rb +7 -2
- data/lib/capybara/spec/views/form.erb +39 -2
- data/lib/capybara/spec/views/frame_child.erb +9 -0
- data/lib/capybara/spec/views/frame_parent.erb +8 -0
- data/lib/capybara/spec/views/with_base_tag.erb +10 -0
- data/lib/capybara/spec/views/with_count.erb +7 -0
- data/lib/capybara/spec/views/with_hover.erb +17 -0
- data/lib/capybara/spec/views/with_html.erb +23 -2
- data/lib/capybara/spec/views/with_js.erb +5 -0
- data/lib/capybara/spec/views/with_scope.erb +6 -1
- data/lib/capybara/spec/views/with_title.erb +1 -0
- data/lib/capybara/spec/views/within_frames.erb +1 -0
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara.rb +43 -62
- data/spec/basic_node_spec.rb +75 -24
- data/spec/dsl_spec.rb +2 -1
- data/spec/rack_test_spec.rb +4 -3
- data/spec/rspec/matchers_spec.rb +105 -17
- data/spec/server_spec.rb +15 -8
- data/spec/spec_helper.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +82 -22
- metadata.gz.sig +0 -0
data/lib/capybara/query.rb
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
module Capybara
|
|
2
2
|
class Query
|
|
3
|
-
attr_accessor :selector, :locator, :options, :
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -62,10 +62,14 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
|
62
62
|
response.status
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
def
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
data/lib/capybara/rails.rb
CHANGED
data/lib/capybara/result.rb
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 :
|
|
35
|
+
class HaveText < Matcher
|
|
36
|
+
attr_reader :type, :content, :options
|
|
35
37
|
|
|
36
|
-
def initialize(
|
|
37
|
-
@
|
|
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?(
|
|
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?(
|
|
51
|
+
@actual.has_no_text?(type, content, options)
|
|
48
52
|
end
|
|
49
53
|
|
|
50
54
|
def failure_message_for_should
|
|
51
|
-
|
|
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
|
-
|
|
61
|
+
failure_message_for_should.sub(/(to find)/, 'not \1')
|
|
56
62
|
end
|
|
57
63
|
|
|
58
64
|
def description
|
|
59
|
-
"
|
|
65
|
+
"text #{format(content)}"
|
|
60
66
|
end
|
|
61
67
|
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
89
|
-
HaveText.new(
|
|
116
|
+
def have_text(*args)
|
|
117
|
+
HaveText.new(*args)
|
|
90
118
|
end
|
|
119
|
+
alias_method :have_content, :have_text
|
|
91
120
|
|
|
92
|
-
def
|
|
93
|
-
|
|
121
|
+
def have_title(title)
|
|
122
|
+
HaveTitle.new(title)
|
|
94
123
|
end
|
|
95
124
|
|
|
96
125
|
def have_link(locator, options={})
|